diff --git a/.gitignore b/.gitignore index 9d8282e9..7834853e 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ release.properties #IntelliJ Project directory .idea/ +**.iml +.java-version diff --git a/.travis.yml b/.travis.yml index 5bd06656..5eeabe77 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,11 @@ +dist: trusty language: java jdk: - oraclejdk8 - openjdk8 +branches: + except: + - logo_submission script: "mvn cobertura:cobertura" diff --git a/README.md b/README.md index 924c0b05..ec19ce54 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,24 @@ -[![Build Status](https://travis-ci.org/eeverman/andhow.svg?branch=master)](https://travis-ci.org/eeverman/andhow) +[![Build Status](https://travis-ci.com/eeverman/andhow.svg?branch=master)](https://travis-ci.com/github/eeverman/andhow) [![codecov](https://codecov.io/gh/eeverman/andhow/branch/master/graph/badge.svg)](https://codecov.io/gh/eeverman/andhow) [![Javadocs](https://www.javadoc.io/badge/org.yarnandtail/andhow.svg)](https://www.javadoc.io/doc/org.yarnandtail/andhow) + + +## New Release: 0.4.1, June 2, 2021 ([notes](https://github.com/eeverman/andhow/releases/tag/andhow-0.4.1)). +AndHow's new logo + +This larger update fixes several issues due to newer JVMs and the IntelliJ IDE. +Special thanks to first time contributor [Vicky Ronnen](https://github.com/VickyRonnen) for fixing Issue [497](https://github.com/eeverman/andhow/issues/497) - +This bug made AndHow unusable for anyone using newer versions of IntelliJ. + + ![Andhow Visual](andhow.gif) -AndHow! strong.valid.simple.AppConfiguration -====== +## AndHow! strong.valid.simple.AppConfiguration AndHow is an easy to use configuration framework with strong typing and detailed validation for web apps, command line or any application environment. _**Learn more at the [AndHow main site](https://sites.google.com/view/andhow)**_ -Key Features --------------- +## Key Features * **Strong Typing** * **Detailed validation** * **Simple to use** @@ -19,58 +27,57 @@ Key Features * **Loads values from multiple sources (JNDI, env vars, prop files, etc)** * **Generates configuration sample file based on application properties** -Questions / Discussion / Contact --------------- +## Questions / Discussion / Contact [Join the discussion](https://sites.google.com/view/andhow/join-discussion) on the [user forum](https://groups.google.com/d/forum/andhowuser) or the *Slack* group (See details on the [Join](https://sites.google.com/view/andhow/join-discussion) page). -Use it via Maven (available on Maven Central) --------------- +## Use it via Maven (available on Maven Central) ```xml org.yarnandtail andhow - 0.4.0 + 0.4.1 ``` -**AndHow can be used in projects with Java 8 and above, however, Java 9 and above have [some restrictions](https://sites.google.com/view/andhow/user-guide/java9)** +**AndHow can be used in projects with Java 8 - Java 15. Work to support Java 16 is underway and will be the next major release. There are [some considerations](https://sites.google.com/view/andhow/user-guide/java9-and-above) for Java 9 and above if your project uses Jigsaw Modules.** -Complete Usage Example --------------- +## Complete Usage Example _**More usage examples and documentation are available at the [AndHow main site](https://sites.google.com/view/andhow)**_ ```java package org.simple; +import org.yarnandtail.andhow.AndHow; import org.yarnandtail.andhow.property.*; public class GettingStarted { - + //1 - final static IntProp COUNT_DOWN_START = IntProp.builder().mustBeNonNull() - .desc("Start the countdown from this number") - .mustBeGreaterThanOrEqualTo(1).defaultValue(2).build(); - + public final static IntProp COUNT_DOWN_START = IntProp.builder().mustBeNonNull() + .mustBeGreaterThanOrEqualTo(1).defaultValue(3).build(); + private final static StrProp LAUNCH_CMD = StrProp.builder().mustBeNonNull() - .desc("What to say when its time to launch") - .mustMatchRegex(".*Go.*").defaultValue("GoGoGo!").build(); - + .desc("What to say when its time to launch") + .mustMatchRegex(".*Go.*").defaultValue("Go-Go-Go!").build(); + public String launch() { String launch = ""; - + //2 for (int i = COUNT_DOWN_START.getValue(); i >= 1; i--) { launch = launch += i + "..."; } - + return launch + LAUNCH_CMD.getValue(); } - + public static void main(String[] args) { - GettingStarted gs = new GettingStarted(); - System.out.println(gs.launch()); + AndHow.findConfig().setCmdLineArgs(args); //3 Optional + + System.out.println( new GettingStarted().launch() ); + System.out.println( LAUNCH_CMD.getValue().replace("Go", "Gone") ); } } ``` @@ -78,42 +85,65 @@ public class GettingStarted { Properties must be `final static`, but may be `private` or any other scope. `builder` methods simplify adding validation, description, defaults and other metadata. -Properties are strongly typed, so default values and validation are specific to -the type, for instance, `StrProp` has regex validation rules for `String`s -while the `IntProp` has greater-than and less-than rules available. +Properties are strongly typed, so default values and validation are type specific, e.g., +`StrProp` has Regex validation while the `IntProp` has GreaterThan / LessThan rules available. ### Section //2 : Using AndHow Properties AndHow Properties are used just like static final constants with an added -`.getValue()` on the end to fetch the value. -Strong typing means that calling `COUNT_DOWN_START.getValue()` +`.getValue()` tacked on. Strong typing means that calling `COUNT_DOWN_START.getValue()` returns an `Integer` while calling `LAUNCH_CMD.getValue()` returns a `String`. +An AndHow Property (and its value) can be accessed anywhere it is visible. +`COUNT_DOWN_START` is public in a public class, so it could be used anywhere, while +`LAUNCH_CMD` is private. +AndHow Properties are always `static`, so they can be accessed in both static +and instance methods, just like this example shows. + +### Section //3 : Accepting Command Line Arguments +If an application needs command line arguments (CLAs), just pass them to AndHow +at startup as this example shows. Properties are referred to using 'dot notation', e.g.: +``` +java -jar GettingStarted.jar org.simple.GettingStarted.LAUNCH_CMD=GoManGo +``` +If you don't need to accept CLA's, you can leave line `//3` out - +AndHow will initialize and startup without any explicit _init_ method when +the first Property is accessed. + ### How do I actually configure some values? We're getting there. The example has defaults for each property so with no other configuration available, -the main method uses the defaults and prints: `3...2...1...GoGoGo!` -Things are more interesting if the default values are removed from the code above. +the main method uses the defaults and prints: +``` +3...2...1...Go-Go-Go! +Gone-Gone-Gone! +``` +Things are more interesting if the default values are removed from the code above: +```java +public final static IntProp COUNT_DOWN_START = IntProp.builder().mustBeNonNull() + .mustBeGreaterThanOrEqualTo(1).build(); //default removed + +private final static StrProp LAUNCH_CMD = StrProp.builder().mustBeNonNull() + .mustMatchRegex(".*Go.*").build(); //default removed +``` Both properties must be non-null, so removing the defaults causes the validation -rules to be violated at startup. Here is an excerpt of the console output when that happens: +rules to be violated at startup. Here is an excerpt from the console when that happens: ``` ======================================================================== Drat! There were AndHow startup errors. Sample configuration files will be written to: '/some_local_tmp_directory/andhow-samples/' ======================================================================== -REQUIRMENT PROBLEMS - When a required property is not provided -Detailed list of Requirements Problems: -Property org.simple.GettingStarted.COUNT_DOWN_START: This Property must be non-null - It must have a non-null default or be loaded by one of the loaders to a non-null value -Property org.simple.GettingStarted.LAUNCH_CMD: This Property must be non-null - It must have a non-null default or be loaded by one of the loaders to a non-null value +Property org.simple.GettingStarted.COUNT_DOWN_START: This Property must be non-null +Property org.simple.GettingStarted.LAUNCH_CMD: This Property must be non-null ======================================================================== ``` -**Validation happens at startup and happens for all properties in the entire application.** -Properties, even those defined in 3rd party jars, are discovered and values for +**AndHow does validation at startup for all properties in the entire application.** +Properties, _even those defined in 3rd party jars_, are discovered and values for them are loaded and validated. -If that fails (as it did above), AndHow throws a RuntimeException to stop +If validation fails (as it did above), AndHow throws a RuntimeException to stop application startup and uses property metadata to generate specific error messages and (helpfully) sample configuration files. -Here is an excerpt of the Java Properties file created when the example code failed validation: +Here is an excerpt of the Java Properties file created when the code above failed validation: ``` # ###################################################################### # Property Group org.simple.GettingStarted @@ -129,15 +159,15 @@ org.simple.GettingStarted.LAUNCH_CMD = [String] AndHow uses all of the provided metadata to create a detailed and well commented configuration file for your project. Insert some real values into that file and place it on your classpath at -/andhow.properties and it will automatically be discovered and loaded at startup. -By default, AndHow automatically discovers and loads configuration from seven common sources. +`/andhow.properties` and it will automatically be discovered and loaded at startup. +By default, AndHow discovers and loads configuration from seven common sources. The default list of configuration loading, in order, is: 1. Fixed values (explicitly set in code for AndHow to use) 2. String[] arguments from the static void main() method -3. System Properties +3. System Properties _(Like the one auto-generated above)_ 4. Environmental Variables 5. JNDI -6. Java properties file on the filesystem (path specified as an AndHow property) +6. Java properties file on the filesystem (path must be specified) 7. Java properties file on the classpath (defaults to /andhow.properties) Property values are set on a first-win basis, so if a property is set as fixed value, @@ -147,3 +177,4 @@ Values passed to the main method take precedence over system properties as so on _**For more examples and documentation, visit the [AndHow main site](https://sites.google.com/view/andhow)**_ _**&?!**_ + diff --git a/andhow-annotation-processor/pom.xml b/andhow-annotation-processor/pom.xml index c7824875..f472f676 100644 --- a/andhow-annotation-processor/pom.xml +++ b/andhow-annotation-processor/pom.xml @@ -3,7 +3,7 @@ org.yarnandtail andhow-parent - 0.4.1-SNAPSHOT + 0.4.2-SNAPSHOT andhow-annotation-processor @@ -38,10 +38,6 @@ - - junit - junit - com.google.testing.compile compile-testing diff --git a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/AndHowCompileException.java b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/AndHowCompileException.java new file mode 100644 index 00000000..3f0b1c5a --- /dev/null +++ b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/AndHowCompileException.java @@ -0,0 +1,80 @@ +package org.yarnandtail.andhow.compile; + +import java.util.Collections; +import java.util.List; + +/** + * Exception thrown by the AndHowCompileProcessor for any type of problem + * during the compile. + * + * This exception can accumulate a list of Problems so that all problems can + * be found in a single pass and for easier testing (this exception can be + * inspected to determine the exact problem). + * + * @author ericeverman + */ +public class AndHowCompileException extends RuntimeException { + + public static final String DEFAULT_MSG = "The AndHowCompileProcessor found a problem" + + " during compilation and threw a fatal exception - " + + "See the error details listed nearby."; + + private final String msg; + private final List problems; + private final Throwable cause; + + + /** + * Instance when there are one or more AndHow domain 'problems' with the + * code being compiled. + * + * Examples would include Properties that are not static final + * or too many init classes on the classpath. + * + * @param problems A list of problems found during compilation. This list + * instance is kept, so no modifications to the list should be made by the + * caller. + */ + public AndHowCompileException(List problems) { + + cause = null; + msg = DEFAULT_MSG; + + this.problems = (problems != null) ? problems : Collections.emptyList(); + } + + /** + * Instance when there is some unexpected, non-AndHow related exception. + * + * Example: Unwritable file system. + * + * @param message AndHow context description + * @param cause Error caught that caused the issue + */ + public AndHowCompileException(String message, Throwable cause) { + msg = message; + this.cause = cause; + this.problems = Collections.emptyList(); + } + + @Override + public synchronized Throwable getCause() { + return (cause != null) ? cause : super.getCause(); + } + + + @Override + public String getMessage() { + return msg; + } + + /** + * The list of AndHow CompileProblems discovered, if any. + * + * @return A non-null, by possibly empty list. Do not modify the returned list. + */ + public List getProblems() { + return problems; + } + +} diff --git a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/AndHowCompileProcessor.java b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/AndHowCompileProcessor.java index 2838edde..ea18e9a2 100644 --- a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/AndHowCompileProcessor.java +++ b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/AndHowCompileProcessor.java @@ -1,105 +1,149 @@ package org.yarnandtail.andhow.compile; -import java.util.logging.Level; import org.yarnandtail.andhow.service.PropertyRegistrationList; -import org.yarnandtail.andhow.service.PropertyRegistration; -import com.sun.source.util.Trees; import java.io.*; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Proxy; import java.util.*; import javax.annotation.processing.*; +import javax.lang.model.SourceVersion; import javax.lang.model.element.*; - import javax.tools.FileObject; import org.yarnandtail.andhow.AndHowInit; import org.yarnandtail.andhow.api.Property; import org.yarnandtail.andhow.service.*; -import org.yarnandtail.andhow.util.AndHowLog; +import static javax.tools.Diagnostic.*; import static javax.tools.StandardLocation.CLASS_OUTPUT; +import org.yarnandtail.andhow.util.TextUtil; /** + * This is the central AndHow compilation class, an Annotation Processor. * - * Note: check to ensure that Props are not referenced in static init blocks b/c - * we may need to load the class (and run its init) before andHow init can - * complete, causing a circular init loop. - * - * @author ericeverman + * An annotation processor nominally reads annotations as classes are compiled. + * This class is annotated {@code SupportedAnnotationTypes("*")}, allowing it to + * 'see' all classes as they are compiled. This class then delegates to a + * 'scanner' class that does deep inspection on compiled code, looking for + * AndHow Properties. + *
+ * When an AndHow Property is found in a class, a new {@code PropertyRegistrar} + * class is created, which contains the list of Properties in that class. + * There is a one-to-one correspondence between user classes that contain + * AndHow Properties and auto-created {@code PropertyRegistrar} classes. + *
+ * At runtime, AndHow will use the {@code ServiceLoader} to discover all instances + * of {@code PropertyRegistrar} on the classpath, thus finding all AndHow + * Property containing classes. */ @SupportedAnnotationTypes("*") public class AndHowCompileProcessor extends AbstractProcessor { - private static final AndHowLog LOG = AndHowLog.getLogger(AndHowCompileProcessor.class); - + private static final String INIT_CLASS_NAME = AndHowInit.class.getCanonicalName(); private static final String TEST_INIT_CLASS_NAME = "org.yarnandtail.andhow.AndHowTestInit"; - + private static final String SERVICES_PACKAGE = ""; - + private static final String SERVICE_REGISTRY_META_DIR = "META-INF/services/"; - + //Static to insure all generated classes have the same timestamp private static Calendar runDate; - private Trees trees; - private final List registrars = new ArrayList(); - + private final List initClasses = new ArrayList(); //List of init classes (should only ever be 1) private final List testInitClasses = new ArrayList(); //List of test init classes (should only ever be 1) + private final List problems = new ArrayList(); //List of problems found. >0== RuntimeException + + /** + * A no-arg constructor is required. + */ public AndHowCompileProcessor() { - //required by Processor API + //used to ensure all metadata files have the same date runDate = new GregorianCalendar(); } + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + //Unwrap the IntelliJ ProcessingEnvironment if needed + super.init(unwrap(processingEnv)); + } + + @Override + public SourceVersion getSupportedSourceVersion() { + //Only scanning for declaration of AndHow Properties, so should + //be immune to most new language constructs. + return SourceVersion.latestSupported(); + } + @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { - boolean isLastRound = roundEnv.processingOver(); - Filer filer = this.processingEnv.getFiler(); + Messager log = this.processingEnv.getMessager(); + boolean isLastRound = roundEnv.processingOver(); if (isLastRound) { - LOG.debug("Final round of annotation processing. Total root element count: {0}", roundEnv.getRootElements().size()); + debug(log, "Final round of annotation processing. Total root element count: {}", + roundEnv.getRootElements().size()); - try { - if (initClasses.size() == 1) { - LOG.info("Found exactly 1 {0} class: {1}", INIT_CLASS_NAME, initClasses.get(0).fullClassName); - writeServiceFile(filer, AndHowInit.class.getCanonicalName(), initClasses); + if (initClasses.size() > 1) { + problems.add(new CompileProblem.TooManyInitClasses( + INIT_CLASS_NAME, initClasses)); + } + + if (testInitClasses.size() > 1) { + problems.add(new CompileProblem.TooManyInitClasses( + TEST_INIT_CLASS_NAME, testInitClasses)); + } - } else if (initClasses.size() > 1) { - TooManyInitClassesException err = - new TooManyInitClassesException(INIT_CLASS_NAME, initClasses); + if (problems.isEmpty()) { + try { + if (initClasses.size() == 1) { - err.writeDetails(LOG); - throw err; - } + debug(log, "Found exactly 1 {} class: {}", + INIT_CLASS_NAME, initClasses.get(0).fullClassName); - if (testInitClasses.size() == 1) { + writeServiceFile(filer, AndHowInit.class.getCanonicalName(), initClasses); - LOG.info("Found exactly 1 {0} class: {1}", TEST_INIT_CLASS_NAME, testInitClasses.get(0).fullClassName); - writeServiceFile(filer, TEST_INIT_CLASS_NAME, testInitClasses); + } - } else if (testInitClasses.size() > 1) { - TooManyInitClassesException err = - new TooManyInitClassesException(TEST_INIT_CLASS_NAME, testInitClasses); + if (testInitClasses.size() == 1) { + debug(log, "Found exactly 1 {} class: {}", + TEST_INIT_CLASS_NAME, testInitClasses.get(0).fullClassName); - err.writeDetails(LOG); - throw err; - } + writeServiceFile(filer, TEST_INIT_CLASS_NAME, testInitClasses); - if (registrars != null && registrars.size() > 0) { - writeServiceFile(filer, PropertyRegistrar.class.getCanonicalName(), registrars); + } + + if (registrars.size() > 0) { + writeServiceFile(filer, PropertyRegistrar.class.getCanonicalName(), registrars); + } + + } catch (IOException e) { + throw new AndHowCompileException("Exception while trying to write generated files", e); } - } catch (IOException e) { - throw new RuntimeException("Exception while trying to write generated files", e); + } else { + error(log, "AndHow Property definition or Init class errors " + + "prevented compilation. Each of the following errors " + + "must be fixed before compilation is possible."); + error(log, "AndHow errors discovered: {}", problems.size()); + + for (CompileProblem err : problems) { + error(log, err.getFullMessage()); + } + + throw new AndHowCompileException(problems); } - + } else { - LOG.trace("Another round of annotation processing. Current root element count: {0}", roundEnv.getRootElements().size()); + debug(log, "Another round of annotation processing. " + + "Current root element count: {}", roundEnv.getRootElements().size()); // @@ -110,7 +154,7 @@ public boolean process(Set annotations, RoundEnvironment TypeElement te = (TypeElement) e; AndHowElementScanner7 st = new AndHowElementScanner7( - this.processingEnv, + this.processingEnv, Property.class.getCanonicalName(), INIT_CLASS_NAME, TEST_INIT_CLASS_NAME); @@ -125,37 +169,23 @@ public boolean process(Set annotations, RoundEnvironment if (ret.hasRegistrations()) { - LOG.debug("Found {0} AndHow Properties in class {1} ", ret.getRegistrations().size(), ret.getRootCanonicalName()); + debug(log, "Found {} AndHow Properties in class {} ", + ret.getRegistrations().size(), ret.getRootCanonicalName()); + PropertyRegistrarClassGenerator gen = new PropertyRegistrarClassGenerator(ret, AndHowCompileProcessor.class, runDate); registrars.add(new CauseEffect(gen.buildGeneratedClassFullName(), te)); PropertyRegistrationList regs = ret.getRegistrations(); - if (LOG.isLoggable(Level.FINEST)) { - for (PropertyRegistration p : ret.getRegistrations()) { - LOG.trace("Found AndHow Property ''{0}'' in root class ''{1}'', immediate parent is ''{2}''", - p.getCanonicalPropertyName(), p.getCanonicalRootName(), p.getJavaCanonicalParentName()); - } - } - try { writeClassFile(filer, gen, e); - LOG.trace("Wrote new generated class file " + gen.buildGeneratedClassSimpleName()); + debug(log, "Wrote new generated class file {}", gen.buildGeneratedClassSimpleName()); } catch (Exception ex) { - LOG.error("Unable to write generated classfile '" + gen.buildGeneratedClassFullName() + "'", ex); + error(log, "Unable to write generated classfile '" + gen.buildGeneratedClassFullName() + "'", ex); throw new RuntimeException(ex); } } - if (ret.getErrors().size() > 0) { - LOG.error( - "AndHow Property definition errors prevented compilation to complete. " + - "Each of the following errors must be fixed before compilation is possible."); - for (String err : ret.getErrors()) { - LOG.error("AndHow Property Error: {0}", err); - } - - throw new RuntimeException("AndHowCompileProcessor threw a fatal exception - See error log for details."); - } + problems.addAll(ret.getProblems()); } } @@ -164,28 +194,47 @@ public boolean process(Set annotations, RoundEnvironment } - public void writeClassFile(Filer filer, PropertyRegistrarClassGenerator generator, Element causingElement) throws Exception { + /** + * Writes a new class implementing the {@code PropertyRegistrar} interface. + * + * The new class directly corresponds to a user classes containing AndHow + * Properties and will contain meta data about the properties. + * + * @param filer The javac file system representation for writing files. + * @param generator AndHow class capable of generating source code for this + * {@code PropertyRegistrar} class. + * @param causingElement A javac Element, which generically refers to any + * piece of source code such as a keyword, class name, etc.. When a file + * is written to the filer, a {@code causingElement} is recorded as metadata + * so there is an association between the file and the reason it was written. + * Likely this is normally used to associate source code line numbers with + * generated code. + * + * @throws Exception If unable to write (out of disc space?) + */ + public void writeClassFile(Filer filer, + PropertyRegistrarClassGenerator generator, Element causingElement) throws Exception { String classContent = generator.generateSource(); - FileObject classFile = filer.createSourceFile(generator.buildGeneratedClassFullName(), causingElement); + FileObject classFile = filer.createSourceFile( + generator.buildGeneratedClassFullName(), causingElement); try (Writer writer = classFile.openWriter()) { writer.write(classContent); - } + } } - - protected void writeServiceFile(Filer filer, - String fullyQualifiedServiceInterfaceName, - List implementingClasses) throws IOException { - + + protected void writeServiceFile(Filer filer, + String fullyQualifiedServiceInterfaceName, List implementingClasses) throws IOException { + //Get a unique causing elements HashSet set = new HashSet(); for (CauseEffect ce : implementingClasses) { set.add(ce.causeElement); } - - + + //The CLASS_OUTPUT location is used instead of SOURCE_OUTPUT because it //seems that non-Java files are not copied over from the SOURCE_OUTPUT //location. @@ -199,21 +248,81 @@ protected void writeServiceFile(Filer filer, writer.write(System.lineSeparator()); } } - + + } + + /** + * Logs a debug message using the javac standard Messager system. + * + * @param log The Message instance to use + * @param pattern String pattern with curly variable replacement like this: {} + * @param args Arguments to put into the {}'s, in order. + */ + void debug(Messager log, String pattern, Object... args) { + log.printMessage(Kind.NOTE, TextUtil.format(pattern, args)); + } + + /** + * Logs an error message using the javac standard Messager system. + * + * @param log The Message instance to use + * @param pattern String pattern with curly variable replacement like this: {} + * @param args Arguments to put into the {}'s, in order. + */ + void error(Messager log, String pattern, Object... args) { + log.printMessage(Kind.ERROR, TextUtil.format(pattern, args)); } - + /** - * Match up a causal Element w/ the Class name that will be registered in + * Match up a causal Element (Basically the compiler representation of a + * class to be compiled) w/ the Class name that will be registered in * a service registry. + * + * When the AnnotationProcessor writes a new file to the Filer, it wants + * a causal Element to associate with it, apparently this info could be + * used for reporting or something. */ protected static class CauseEffect { String fullClassName; Element causeElement; - + public CauseEffect(String fullClassName, Element causeElement) { this.fullClassName = fullClassName; this.causeElement = causeElement; } } + /** + * With the introduction of IntelliJ Idea 2020.3 release the ProcessingEnvironment + * is not of type com.sun.tools.javac.processing.JavacProcessingEnvironment + * but java.lang.reflect.Proxy. + * The com.sun.source.util.Trees.instance() throws an IllegalArgumentException when the proxied processingEnv is passed. + * + * @author Vicky Ronnen (vicky.ronnen@upcmail.nl or v.ronnen@gmail.com) + * @link https://youtrack.jetbrains.com/issue/IDEA-256707 + * @param processingEnv possible proxied + * @return ProcessingEnvironment unwrapped from the proxy if proxied or the original processingEnv + */ + private ProcessingEnvironment unwrap(ProcessingEnvironment processingEnv) { + if (Proxy.isProxyClass(processingEnv.getClass())) { + InvocationHandler invocationHandler = Proxy.getInvocationHandler(processingEnv); + try { + Field field = invocationHandler.getClass().getDeclaredField("val$delegateTo"); + field.setAccessible(true); + Object o = field.get(invocationHandler); + if (o instanceof ProcessingEnvironment) { + return (ProcessingEnvironment) o; + } else { + processingEnv.getMessager().printMessage(Kind.ERROR, "got " + o.getClass() + " expected instanceof com.sun.tools.javac.processing.JavacProcessingEnvironment"); + return null; + } + } catch (NoSuchFieldException | IllegalAccessException e) { + processingEnv.getMessager().printMessage(Kind.ERROR, e.getMessage()); + return null; + } + } else { + return processingEnv; + } + } + } diff --git a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/AndHowElementScanner7.java b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/AndHowElementScanner7.java index fed4c76e..9fcad8d8 100644 --- a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/AndHowElementScanner7.java +++ b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/AndHowElementScanner7.java @@ -78,9 +78,9 @@ public CompileUnit visitVariable(VariableElement e, String p) { ts.scan(trees.getPath(e), marker); if (marker.isNewProperty()) { compileUnit.addProperty( - new SimpleVariable(e.getSimpleName().toString(), + e.getSimpleName().toString(), e.getModifiers().contains(Modifier.STATIC), - e.getModifiers().contains(Modifier.FINAL)) + e.getModifiers().contains(Modifier.FINAL) ); if (LOG.isLoggable(Level.FINE)) { diff --git a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/CompileProblem.java b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/CompileProblem.java new file mode 100644 index 00000000..2ac5d68e --- /dev/null +++ b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/CompileProblem.java @@ -0,0 +1,253 @@ +package org.yarnandtail.andhow.compile; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import org.yarnandtail.andhow.util.TextUtil; + +/** + * Implementation represent specific problems with AndHow Property usage or + * initiation, discovered during compilation. + */ +public abstract class CompileProblem { + + final String groupName; + final String propName; + final boolean propProblem; + + /** + * Base constructor for Property related problems. + * + * @param groupName Full name of the class containing the {@code Property}. + * If an inner class, should be the complete name w/ the inner class. + * @param propName The declared name of the {@code Property} w/ the problem. + */ + CompileProblem(String groupName, String propName) { + this.groupName = groupName; + this.propName = propName; + propProblem = true; + } + + /** + * Base constructor for non-Property related problems, such as Initiation + * errors. + */ + CompileProblem() { + groupName = null; + propName = null; + propProblem = false; + } + + /** + * Full name of the class containing the {@code Property}. + * In the case of an inner class, this should be the complete name including + * that inner class. + * + * @return May be null if this is not a property related problem. + */ + public String getGroupName() { + return groupName; + } + + /** + * The declared name of the {@code Property} that has the problem. + * + * @return May be null if this is not a property related problem. + */ + public String getPropertyName() { + return propName; + } + + /** + * If true, the problem is related to a specific {@code Property}. + * @return + */ + public boolean isPropertyProblem() { + return propProblem; + } + + /** + * Logical equals implementation based on equality of fields. + * + * This is only used for comparisons during testing. + * + * @param o Compared to me. + * @return True if logically equal. + */ + @Override + public boolean equals(Object o) { + if (o != null && this.getClass().isInstance(o)) { + CompileProblem cp = (CompileProblem)o; + + return + this.groupName.equals(cp.groupName) && + this.propName.equals(cp.propName) && + this.propProblem == cp.propProblem; + } else { + return false; + } + } + + /** + * The complete message that will be logged during compilation. + * + * Intended to be user readable w/ enough info for the user to understand + * and find the issue. + * + * @return A String explanation of the problem. + */ + public abstract String getFullMessage(); + + /** + * An AndHow Property is missing the {@code static} modifier. + */ + static class PropMissingStatic extends CompileProblem { + + /** + * New instance. + * + * @param groupName Full name of the class containing the {@code Property}. + * If an inner class, should be the complete name w/ the inner class. + * @param propName The declared name of the {@code Property} w/ the problem. + */ + public PropMissingStatic(String groupName, String propName) { + super(groupName, propName); + } + + @Override + public String getFullMessage() { + return TextUtil.format( + "The AndHow Property '{}' in the class '{}' must be declared as static.", + groupName, propName); + } + } + + /** + * An AndHow Property is missing the {@code final} modifier. + */ + static class PropMissingFinal extends CompileProblem { + + /** + * New instance. + * + * @param groupName Full name of the class containing the {@code Property}. + * If an inner class, should be the complete name w/ the inner class. + * @param propName The declared name of the {@code Property} w/ the problem. + */ + public PropMissingFinal(String groupName, String propName) { + super(groupName, propName); + } + + @Override + public String getFullMessage() { + return TextUtil.format( + "The AndHow Property '{}' in the class '{}' must be declared as final.", + groupName, propName); + } + } + + /** + * An AndHow Property is missing the {@code static} and {@code final} modifiers. + */ + static class PropMissingStaticFinal extends CompileProblem { + + /** + * New instance. + * + * @param groupName Full name of the class containing the {@code Property}. + * If an inner class, should be the complete name w/ the inner class. + * @param propName The declared name of the {@code Property} w/ the problem. + */ + public PropMissingStaticFinal(String groupName, String propName) { + super(groupName, propName); + } + + @Override + public String getFullMessage() { + return TextUtil.format( + "The AndHow Property '{}' in the class '{}' must be declared as final.", + groupName, propName); + } + } + + /** + * More than a single Init class was found on the classpath. + */ + static class TooManyInitClasses extends CompileProblem { + private final List _instances = new ArrayList(); + private final String _fullInitClassName; + + /** + * New instance. + * + * @param fullInitClassName The Init interface name + * @param instances A list of instances that implement the interface. + */ + public TooManyInitClasses( + String fullInitClassName, + List instances) { + + if (instances != null) this._instances.addAll(instances); + this._fullInitClassName = fullInitClassName; + + } + + /** + * A list of full class names that implement the InitClassName interface. + * + * @return A List of class names. Never null. + */ + public List getInstanceNames() { + List names = new ArrayList(); + + for (AndHowCompileProcessor.CauseEffect ce : _instances) { + names.add(ce.fullClassName); + } + + return names; + } + + /** + * The Init interface name. + * + * @return A class name. + */ + public String getInitClassName() { + return _fullInitClassName; + } + + @Override + public String getFullMessage() { + String impList = _instances.stream() + .map(i -> i.fullClassName).collect(Collectors.joining( ", " )); + + return TextUtil.format("Multiple ({}) implementations of {} were found, " + + "but only one is allowed. Implementations found: {}", + String.valueOf(_instances.size()), + _fullInitClassName, impList); + } + + /** + * Logical equals implementation based on equality of fields. + * + * This is only used for comparisons during testing. + * + * @param o Compared to me. + * @return True if logically equal. + */ + @Override + public boolean equals(Object o) { + if (o != null && this.getClass().isInstance(o)) { + TooManyInitClasses cp = (TooManyInitClasses)o; + + return + this._instances.containsAll(cp._instances) && + cp._instances.containsAll(this._instances) && + this._fullInitClassName.equals(cp._fullInitClassName); + } else { + return false; + } + } + } + +} diff --git a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/CompileUnit.java b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/CompileUnit.java index 0b470a3c..6c262709 100644 --- a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/CompileUnit.java +++ b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/CompileUnit.java @@ -32,7 +32,7 @@ public class CompileUnit { private final String classCanonName; private PropertyRegistrationList registrations; //late init - private List errors; //late init + private List errors; //late init private boolean initClass; //True if an AndHowInit instance (and not AndHowTestInit) private boolean testInitClass; //True if an AndHowTestInit instance @@ -101,45 +101,42 @@ public SimpleType popType() { * If modifiers are invalid, an error will be recorded rather than a * Property. * - * @param variableElement A SimpleType representing a variable to which an - * AndHow property is constructed and assigned to. + * @param name The name of the variable the Property is assigned to. + * @param _static Does the variable has the static modifier? + * @param _final Is the variable declared as static? * @return True if the property could be added, false if an error was * recorded instead. */ - public boolean addProperty(SimpleVariable variableElement) { + public boolean addProperty(String name, boolean _static, boolean _final) { - if (variableElement.isStatic() && variableElement.isFinal()) { + if (_static && _final) { if (registrations == null) { registrations = new PropertyRegistrationList(classCanonName); } - registrations.add(variableElement.getName(), getInnerPathNames()); + registrations.add(name, getInnerPathNames()); return true; } else { - addPropertyError(variableElement.getName(), "New AndHow Properties must be assigned to a static final field."); + + if (errors == null) { + errors = new ArrayList(); + } + + String parentName = NameUtil.getJavaName(classCanonName, this.getInnerPathNames()); + + if (_static) { + errors.add(new CompileProblem.PropMissingFinal(parentName, name)); + } else if (_final) { + errors.add(new CompileProblem.PropMissingStatic(parentName, name)); + } else { + errors.add(new CompileProblem.PropMissingStaticFinal(parentName, name)); + } + return false; } } - /** - * Register an AndHow Property declaration in the current scope - either - * directly in the the top level class or the recorded path to an inner - * class. - * - * If modifiers are invalid, an error will be recorded rather than a - * Property. - * - * @param name The name of the variable the Property is assigned to. - * @param _static Does the variable has the static modifier? - * @param _final Is the variable declared as static? - * @return True if the property could be added, false if an error was - * recorded instead. - */ - public boolean addProperty(String name, boolean _static, boolean _final) { - return addProperty(new SimpleVariable(name, _static, _final)); - } - /** * Return the state of inner class nesting from the outermost to the * innermost. @@ -147,7 +144,7 @@ public boolean addProperty(String name, boolean _static, boolean _final) { * If the current state is at the root of the top level class, an empty list * is returned. * - * @see getInnerPathNames() for just the names of the nested inner classes. + * @see #getInnerPathNames() for just the names of the nested inner classes. * @return */ public List getInnerPath() { @@ -193,17 +190,6 @@ public List getInnerPathNames() { return pathNames; } - public void addPropertyError(String propName, String msg) { - - if (errors == null) { - errors = new ArrayList(); - } - - String parentName = NameUtil.getJavaName(classCanonName, this.getInnerPathNames()); - - errors.add("The AndHow Property '" + propName + "' in " + parentName + " is invalid: " + msg); - } - /** * The fully qualified name of the top level class this CompileUnit is for. * @@ -263,16 +249,16 @@ public PropertyRegistrationList getRegistrations() { * * @return */ - public List getErrors() { + public List getProblems() { if (errors != null) { return errors; } else { - return Collections.EMPTY_LIST; + return Collections.emptyList(); } } /** - * Returns true if the getErrors() list would be non-empty. + * Returns true if the getProblems() list would be non-empty. * * @return */ diff --git a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/CompileUtil.java b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/CompileUtil.java new file mode 100644 index 00000000..9ea3e415 --- /dev/null +++ b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/CompileUtil.java @@ -0,0 +1,70 @@ +package org.yarnandtail.andhow.compile; + +/** + * Utilities for the AndHow AnnotationProcessor. + */ +public class CompileUtil { + + /** + * Determine the correct 'Generated' annotation class name based on the current Java runtime. + * + * This method fetches the version of the current runtime via getMajorJavaVersion() and + * uses that to call getGeneratedAnnotationClassName(int). + * + * @return A the fully qualified class name of the Generated annotation. + */ + public static String getGeneratedAnnotationClassName() { + return getGeneratedAnnotationClassName(getMajorJavaVersion(System.getProperty("java.version"))); + } + + /** + * Determine the correct 'Generated' annotation class name based on the Java major version. + * Java 8 uses the @javax.annotation.Generated annotation to mark a generated class. + * Java 9 and beyond uses @javax.annotation.processing.Generated + * + * Both annotations are SOURCE level retention, so they are only present in the source code and no + * record of them is compiled into the binary. Thus, the determination of which one to use is + * based only on the Java version used to compile, not the -source or -target settings. + * + * @param javaMajorVersion The Java version in integer form. Use '8' for 1.8. + * @return A the fully qualified class name of the Generated annotation. + */ + public static String getGeneratedAnnotationClassName(int javaMajorVersion) { + if (javaMajorVersion < 9) { + return "javax.annotation.Generated"; + } else { + return "javax.annotation.processing.Generated"; + } + } + + /** + * Determine the major version of the Java runtime based on a version string. + * + * All versions are integers, thus version `1.8` returns `8`. + * Java 10 introduces the Runtime.version(), which would remove the need for this method, + * however, at the moment the code is still Java 8 compatable. + * + * @param versionString As returned from SystemProperties.getProperty("java.version") + * @return + */ + public static int getMajorJavaVersion(String versionString) { + String[] versionParts = versionString.split("[\\.\\-_]", 3); + + try { + if ("1".equals(versionParts[0])) { + //Old style 1.x format + return Integer.parseInt(versionParts[1]); + } else { + return Integer.parseInt(versionParts[0]); + } + + } catch (NumberFormatException e) { + throw new RuntimeException( + "AndHow couldn't parse '" + versionString + "' as a 'java.version' string in System.properties. " + + "Is this a non-standard JDK? ", e + ); + } + + + } +} diff --git a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/PropertyRegistrarClassGenerator.java b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/PropertyRegistrarClassGenerator.java index 5e50a83b..8cb598bb 100644 --- a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/PropertyRegistrarClassGenerator.java +++ b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/PropertyRegistrarClassGenerator.java @@ -24,7 +24,8 @@ public class PropertyRegistrarClassGenerator { * Create a new instance w all info needed to generateSource a PropertyRegistrar file. * * @param compUnit CompileUnit instance w/ all needed class and property info - * @param generatingClass The class (likely an AnnotationProcessor) that will be annotated as the generator + * @param generatingClass The class of our AnnotationProcessor to be listed as the generator in the + * Generated annotation. * @param runDate The Calendar date-time of the run, used for annotation. * Passed in so all generated files can have the same timestamp. */ @@ -42,6 +43,7 @@ public String getTemplatePath() { public String getTemplate() throws Exception { return IOUtil.getUTF8ResourceAsString(getTemplatePath()); } + public String generateSource() throws Exception { @@ -50,11 +52,14 @@ public String generateSource() throws Exception { String source = String.format(template, buildPackageString(), - compUnit.getRootCanonicalName(), compUnit.getRootSimpleName(), + compUnit.getRootCanonicalName(), + compUnit.getRootSimpleName(), buildGeneratedClassSimpleName(), PropertyRegistrar.class.getCanonicalName(), - generatingClass.getCanonicalName(), buildRunDateString(), - buildRegistrationAddsString() + generatingClass.getCanonicalName(), + buildRunDateString(), + buildRegistrationAddsString(), + CompileUtil.getGeneratedAnnotationClassName() ); return source; diff --git a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/SimpleVariable.java b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/SimpleVariable.java deleted file mode 100644 index 204cfe14..00000000 --- a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/SimpleVariable.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.yarnandtail.andhow.compile; - -/** - * Compile time representation of a variable w/ just enough information - * to decide if it is a valid variable for an AndHow Property to be constructed - * and assigned to. - */ -public class SimpleVariable { - - private final String name; - private final boolean _static; - private final boolean _final; - - /** - * - * @param name Name of this variable - * @param _static Is this var marked as static? - * @param _final Is this var marked as final? - */ - public SimpleVariable(String name, boolean _static, boolean _final) { - this.name = name; - this._static = _static; - this._final = _final; - } - - public String getName() { - return name; - } - - public boolean isStatic() { - return _static; - } - - public boolean isFinal() { - return _final; - } - - -} diff --git a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/TooManyInitClassesException.java b/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/TooManyInitClassesException.java deleted file mode 100644 index 5604ed89..00000000 --- a/andhow-annotation-processor/src/main/java/org/yarnandtail/andhow/compile/TooManyInitClassesException.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.yarnandtail.andhow.compile; - -import java.util.ArrayList; -import java.util.List; -import org.yarnandtail.andhow.compile.AndHowCompileProcessor.CauseEffect; -import org.yarnandtail.andhow.util.AndHowLog; - -/** - * - * @author ericeverman - */ -public class TooManyInitClassesException extends RuntimeException { - protected List _instances = new ArrayList(); - protected String _fullInitClassName; - - public TooManyInitClassesException( - String fullInitClassName, - List instances) { - if (instances != null) this._instances.addAll(instances); - this._fullInitClassName = fullInitClassName; - } - - public List getInstanceNames() { - List names = new ArrayList(); - - for (CauseEffect ce : _instances) { - names.add(ce.fullClassName); - } - - return names; - } - - public void writeDetails(AndHowLog log) { - log.error("Multiple ({0}) {1} implementation classes were found, but only " - + "one is allowed. List follows:", - Integer.valueOf(_instances.size()).toString(), _fullInitClassName); - - for (String name : getInstanceNames()) { - log.error("\t* " + name); - } - } - - @Override - public String getMessage() { - return "Multiple " + _fullInitClassName + " implementations were found - " - + "only one is allowed. See System.err for complete list"; - } - - -} diff --git a/andhow-annotation-processor/src/main/resources/org/yarnandtail/andhow/compile/PropertyRegistrarClassGenerator_Template.txt b/andhow-annotation-processor/src/main/resources/org/yarnandtail/andhow/compile/PropertyRegistrarClassGenerator_Template.txt index 9af12a1c..a5833bd8 100644 --- a/andhow-annotation-processor/src/main/resources/org/yarnandtail/andhow/compile/PropertyRegistrarClassGenerator_Template.txt +++ b/andhow-annotation-processor/src/main/resources/org/yarnandtail/andhow/compile/PropertyRegistrarClassGenerator_Template.txt @@ -4,12 +4,15 @@ import org.yarnandtail.andhow.service.AbstractPropertyRegistrar; import org.yarnandtail.andhow.service.PropertyRegistrationList; /* -Java9 places 'Generated' in a module that needs to be separate included in a build -or brought in as a dependency. As a result, just using a comment instead. -@javax.annotation.Generated( +AndHow generated class that is discovered via the Service Provider API to +register a proxied class as having AndHow properties. These properties +can then be auto-discovered and assigned values at startup. +See: https://github.com/eeverman/andhow +*/ +@%9$s( value="%6$s", date="%7$s", - comments="Proxy for %2$s registered as a service provider in META-INF/services/%5$s") */ + comments="Proxy for %2$s registered as a service provider in META-INF/services/%5$s") public class %4$s extends AbstractPropertyRegistrar { @Override diff --git a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/AndHowCompileExceptionTest.java b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/AndHowCompileExceptionTest.java new file mode 100644 index 00000000..adca2e95 --- /dev/null +++ b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/AndHowCompileExceptionTest.java @@ -0,0 +1,51 @@ +package org.yarnandtail.andhow.compile; + +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +/** + * + * @author ericeverman + */ +public class AndHowCompileExceptionTest { + + @Test + public void testAllForProblemConstructor() { + + //Setup + List problems = new ArrayList(); + CompileProblem.PropMissingStaticFinal prob1 = new CompileProblem.PropMissingStaticFinal("g1", "p1"); + CompileProblem.PropMissingStaticFinal prob2 = new CompileProblem.PropMissingStaticFinal("g1", "p1"); + problems.clear(); + problems.add(prob1); + problems.add(prob2); + + + AndHowCompileException ahce = new AndHowCompileException(problems); + assertNull(ahce.getCause()); + assertEquals(AndHowCompileException.DEFAULT_MSG, ahce.getMessage()); + assertTrue(problems.containsAll(ahce.getProblems())); + assertTrue(ahce.getProblems().containsAll(problems)); + } + + @Test + public void testNullProblemsShouldNotBombr() { + + AndHowCompileException ahce = new AndHowCompileException(null); + assertEquals(0, ahce.getProblems().size()); + } + + @Test + public void testAllForMessageThrowableConstructor() { + + final String MSG = "ABC"; + final Exception E = new Exception(); + + AndHowCompileException ahce = new AndHowCompileException(MSG, E); + assertEquals(E, ahce.getCause()); + assertEquals(MSG, ahce.getMessage()); + assertEquals(0, ahce.getProblems().size()); + } +} diff --git a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/AndHowCompileProcessorTest.java b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/AndHowCompileProcessorTest.java deleted file mode 100644 index 2e64408d..00000000 --- a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/AndHowCompileProcessorTest.java +++ /dev/null @@ -1,260 +0,0 @@ -package org.yarnandtail.andhow.compile; - -import org.yarnandtail.compile.*; -import org.yarnandtail.andhow.service.PropertyRegistrar; -import org.yarnandtail.andhow.service.PropertyRegistration; -import java.io.*; -import java.nio.charset.Charset; -import java.util.*; -import javax.tools.*; -import org.junit.Test; -import org.yarnandtail.andhow.util.IOUtil; - -import static org.junit.Assert.*; - -/** - * A lot of this code was borrowed from here: - * https://gist.github.com/johncarl81/46306590cbdde5a3003f - * @author ericeverman - */ -public class AndHowCompileProcessorTest { - - // - //Names of classes in the resources directory, used for compile testing of - //initiation self discovery. The 'A' class extends the 'Abstract' one for each. - protected static final String AndHowInitAbstract_NAME = "org.yarnandtail.andhow.compile.AndHowInitAbstract"; - protected static final String AndHowInitA_NAME = "org.yarnandtail.andhow.compile.AndHowInitA"; - protected static final String AndHowInitB_NAME = "org.yarnandtail.andhow.compile.AndHowInitB"; - protected static final String AndHowTestInitAbstract_NAME = "org.yarnandtail.andhow.compile.AndHowTestInitAbstract"; - protected static final String AndHowTestInitA_NAME = "org.yarnandtail.andhow.compile.AndHowTestInitA"; - protected static final String AndHowTestInitB_NAME = "org.yarnandtail.andhow.compile.AndHowTestInitB"; - - - @Test - public void testCompileAnnotationProcessorOutput() throws Exception { - - final String CLASS_PACKAGE = "org.yarnandtail.andhow.compile"; - final String CLASS_NAME = CLASS_PACKAGE + ".PropertySample"; - final String CLASS_SOURCE_PATH = "/" + CLASS_NAME.replace(".", "/") + ".java"; - final String GEN_CLASS_NAME = CLASS_PACKAGE + ".$PropertySample_AndHowProps"; - - JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - final MemoryFileManager manager = new MemoryFileManager(compiler); - TestClassLoader loader = new TestClassLoader(manager); - - List options=new ArrayList(); - //options.add("-verbose"); - - - Set input = new HashSet(); - String classContent = IOUtil.getUTF8ResourceAsString(CLASS_SOURCE_PATH); - input.add(new TestSource(CLASS_NAME, JavaFileObject.Kind.SOURCE, classContent)); - - JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, input); - task.setProcessors(Collections.singleton(new AndHowCompileProcessor())); - task.call(); - - Object genClass = loader.loadClass(GEN_CLASS_NAME).newInstance(); - String genSvsFile = IOUtil.toString(loader.getResourceAsStream("/META-INF/services/org.yarnandtail.andhow.service.PropertyRegistrar"), Charset.forName("UTF-8")); - - assertNotNull(genClass); - - PropertyRegistrar registrar = (PropertyRegistrar)genClass; - - //final String ROOT = "org.yarnandtail.andhow.compile.PropertySample"; - - assertEquals(CLASS_NAME, registrar.getRootCanonicalName()); - List propRegs = registrar.getRegistrationList(); - - assertEquals(CLASS_NAME + ".STRING", propRegs.get(0).getCanonicalPropertyName()); - assertEquals(CLASS_NAME + ".STRING_PUB", propRegs.get(1).getCanonicalPropertyName()); - assertEquals(CLASS_NAME + ".PC.STRING", propRegs.get(2).getCanonicalPropertyName()); - assertEquals(CLASS_NAME + ".PC.STRING_PUB", propRegs.get(3).getCanonicalPropertyName()); - assertEquals(CLASS_NAME + ".PC.PC_PC.STRING", propRegs.get(4).getCanonicalPropertyName()); - assertEquals(CLASS_NAME + ".PC.PC_PI.STRING", propRegs.get(5).getCanonicalPropertyName()); - - assertEquals(CLASS_NAME + ".PI.STRING", propRegs.get(6).getCanonicalPropertyName()); - assertEquals(CLASS_NAME + ".PI.PI_DC.STRING", propRegs.get(7).getCanonicalPropertyName()); - assertEquals(CLASS_NAME + ".PI.PI_DC.STRING_PUB", propRegs.get(8).getCanonicalPropertyName()); - assertEquals(CLASS_NAME + ".PI.PI_DI.STRING", propRegs.get(9).getCanonicalPropertyName()); - assertEquals(CLASS_NAME + ".PI.PI_DI.STRING_PUB", propRegs.get(10).getCanonicalPropertyName()); - - // - //Test the registration file - assertNotNull(genSvsFile); - assertEquals(GEN_CLASS_NAME, genSvsFile.trim()); - } - - - @Test - public void testServiceRegistrationOfOneProdAndOneTestInit() throws Exception { - - JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - final MemoryFileManager manager = new MemoryFileManager(compiler); - TestClassLoader loader = new TestClassLoader(manager); - - List options=new ArrayList(); - //options.add("-verbose"); - - - Set input = new HashSet(); - input.add(new TestSource(AndHowInitAbstract_NAME)); //abstract should be ignored - input.add(new TestSource(AndHowInitA_NAME)); - input.add(new TestSource(AndHowTestInitAbstract_NAME)); - input.add(new TestSource(AndHowTestInitA_NAME)); - - JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, input); - task.setProcessors(Collections.singleton(new AndHowCompileProcessor())); - task.call(); - - String prodInitSvs = IOUtil.toString(loader.getResourceAsStream("/META-INF/services/org.yarnandtail.andhow.AndHowInit"), Charset.forName("UTF-8")); - String testInitSvs = IOUtil.toString(loader.getResourceAsStream("/META-INF/services/org.yarnandtail.andhow.AndHowTestInit"), Charset.forName("UTF-8")); - - - // - //Test the initiation files - assertNotNull(prodInitSvs); - assertEquals(AndHowInitA_NAME, prodInitSvs.trim()); - assertNotNull(testInitSvs); - assertEquals(AndHowTestInitA_NAME, testInitSvs.trim()); - } - - - @Test - public void testServiceRegistrationOfOneProdInit() throws Exception { - - JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - final MemoryFileManager manager = new MemoryFileManager(compiler); - TestClassLoader loader = new TestClassLoader(manager); - - List options=new ArrayList(); - //options.add("-verbose"); - - - Set input = new HashSet(); - input.add(new TestSource(AndHowInitAbstract_NAME)); - input.add(new TestSource(AndHowInitA_NAME)); - - JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, input); - task.setProcessors(Collections.singleton(new AndHowCompileProcessor())); - task.call(); - - String prodInitSvs = IOUtil.toString(loader.getResourceAsStream("/META-INF/services/org.yarnandtail.andhow.AndHowInit"), Charset.forName("UTF-8")); - - // - //Test the initiation files - assertNotNull(prodInitSvs); - assertEquals(AndHowInitA_NAME, prodInitSvs.trim()); - } - - @Test - public void testServiceRegistrationOfOneTestInit() throws Exception { - - JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - final MemoryFileManager manager = new MemoryFileManager(compiler); - TestClassLoader loader = new TestClassLoader(manager); - - List options=new ArrayList(); - //options.add("-verbose"); - - - Set input = new HashSet(); - input.add(new TestSource(AndHowTestInitAbstract_NAME)); - input.add(new TestSource(AndHowTestInitA_NAME)); - - JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, input); - task.setProcessors(Collections.singleton(new AndHowCompileProcessor())); - task.call(); - - String testInitSvs = IOUtil.toString(loader.getResourceAsStream("/META-INF/services/org.yarnandtail.andhow.AndHowTestInit"), Charset.forName("UTF-8")); - - - // - //Test the initiation files - assertNotNull(testInitSvs); - assertEquals(AndHowTestInitA_NAME, testInitSvs.trim()); - } - - - @Test - public void testServiceRegistrationOfAndHowInitWithTooManyProdInstances() throws Exception { - - try { - JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - final MemoryFileManager manager = new MemoryFileManager(compiler); - TestClassLoader loader = new TestClassLoader(manager); - - List options=new ArrayList(); - //options.add("-verbose"); - - - Set input = new HashSet(); - input.add(new TestSource(AndHowInitAbstract_NAME)); - input.add(new TestSource(AndHowInitA_NAME)); - input.add(new TestSource(AndHowInitB_NAME)); - - JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, input); - task.setProcessors(Collections.singleton(new AndHowCompileProcessor())); - task.call(); - - } catch (RuntimeException e) { - TooManyInitClassesException tmi = null; - - if (e instanceof TooManyInitClassesException) { - tmi = (TooManyInitClassesException)e; - } else if (e.getCause() != null && e.getCause() instanceof TooManyInitClassesException) { - tmi = (TooManyInitClassesException) e.getCause(); - } - - if (tmi != null) { - assertEquals(2, tmi.getInstanceNames().size()); - assertTrue(tmi.getInstanceNames().contains(AndHowInitA_NAME)); - assertTrue(tmi.getInstanceNames().contains(AndHowInitB_NAME)); - } else { - fail("Expecting the exception to be TooManyInitClassesException or caused by it."); - } - } - } - - - @Test - public void testServiceRegistrationOfAndHowInitWithTooManyTestInstances() throws Exception { - - try { - JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); - final MemoryFileManager manager = new MemoryFileManager(compiler); - TestClassLoader loader = new TestClassLoader(manager); - - List options=new ArrayList(); - //options.add("-verbose"); - - - Set input = new HashSet(); - input.add(new TestSource(AndHowTestInitAbstract_NAME)); - input.add(new TestSource(AndHowTestInitA_NAME)); - input.add(new TestSource(AndHowTestInitB_NAME)); - - JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, input); - task.setProcessors(Collections.singleton(new AndHowCompileProcessor())); - task.call(); - - } catch (RuntimeException e) { - TooManyInitClassesException tmi = null; - - if (e instanceof TooManyInitClassesException) { - tmi = (TooManyInitClassesException)e; - } else if (e.getCause() != null && e.getCause() instanceof TooManyInitClassesException) { - tmi = (TooManyInitClassesException) e.getCause(); - } - - if (tmi != null) { - assertEquals(2, tmi.getInstanceNames().size()); - assertTrue(tmi.getInstanceNames().contains(AndHowTestInitA_NAME)); - assertTrue(tmi.getInstanceNames().contains(AndHowTestInitB_NAME)); - } else { - fail("Expecting the exception to be TooManyInitClassesException or caused by it."); - } - } - } - -} diff --git a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/AndHowCompileProcessor_InitTest.java b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/AndHowCompileProcessor_InitTest.java new file mode 100644 index 00000000..aa806fc4 --- /dev/null +++ b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/AndHowCompileProcessor_InitTest.java @@ -0,0 +1,261 @@ +package org.yarnandtail.andhow.compile; + +import org.yarnandtail.compile.*; +import static org.yarnandtail.andhow.compile.CompileProblem.*; +import java.nio.charset.Charset; +import java.util.*; +import javax.tools.*; +import javax.tools.Diagnostic.Kind; +import org.junit.jupiter.api.Test; +import org.yarnandtail.andhow.util.IOUtil; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * A lot of this code was borrowed from here: + * https://gist.github.com/johncarl81/46306590cbdde5a3003f + * @author ericeverman + */ +public class AndHowCompileProcessor_InitTest extends AndHowCompileProcessorTestBase { + + static final String pkg = AndHowCompileProcessor_PropertyTest.class.getPackage().getName(); + + // + //Names of classes in the resources directory, used for compile testing of + //initiation self discovery. The 'A' class extends the 'Abstract' one for each. + protected static final String AndHowInitAbstract_NAME = "AndHowInitAbstract"; + protected static final String AndHowInitA_NAME = "AndHowInitA"; + protected static final String AndHowInitB_NAME = "AndHowInitB"; + protected static final String AndHowTestInitAbstract_NAME = "AndHowTestInitAbstract"; + protected static final String AndHowTestInitA_NAME = "AndHowTestInitA"; + protected static final String AndHowTestInitB_NAME = "AndHowTestInitB"; + + @Test + public void testServiceRegistrationOfOneProdAndOneTestInit() throws Exception { + + List options=new ArrayList(); + //options.add("-verbose"); + + sources.add(buildTestSource(pkg, AndHowInitAbstract_NAME)); //Abstract should be igored + sources.add(buildTestSource(pkg, AndHowInitA_NAME)); + sources.add(buildTestSource(pkg, AndHowTestInitAbstract_NAME)); + sources.add(buildTestSource(pkg, AndHowTestInitA_NAME)); + + JavaCompiler.CompilationTask task = compiler.getTask(null, manager, diagnostics, options, null, sources); + task.setProcessors(Collections.singleton(new AndHowCompileProcessor())); + task.call(); + + String prodInitSvs = IOUtil.toString(loader.getResourceAsStream(INIT_SVS_PATH), Charset.forName("UTF-8")); + String testInitSvs = IOUtil.toString(loader.getResourceAsStream(TEST_INIT_SVS_PATH), Charset.forName("UTF-8")); + + assertEquals(0, + diagnostics.getDiagnostics().stream().filter( + d -> d.getKind().equals(Kind.ERROR) || d.getKind().equals(Kind.WARNING) + ).count(), + "Should be no warn/errors"); + + // + //Test the initiation files + assertNotNull(prodInitSvs); + assertEquals(fullName(pkg, AndHowInitA_NAME), prodInitSvs.trim()); + assertNotNull(testInitSvs); + assertEquals(fullName(pkg, AndHowTestInitA_NAME), testInitSvs.trim()); + } + + + @Test + public void testServiceRegistrationOfOneProdInit() throws Exception { + + List options=new ArrayList(); + //options.add("-verbose"); + + sources.add(buildTestSource(pkg, AndHowInitAbstract_NAME)); + sources.add(buildTestSource(pkg, AndHowInitA_NAME)); + + JavaCompiler.CompilationTask task = compiler.getTask(null, manager, diagnostics, options, null, sources); + task.setProcessors(Collections.singleton(new AndHowCompileProcessor())); + task.call(); + + String prodInitSvs = IOUtil.toString(loader.getResourceAsStream(INIT_SVS_PATH), Charset.forName("UTF-8")); + + assertEquals(0, + diagnostics.getDiagnostics().stream().filter( + d -> d.getKind().equals(Kind.ERROR) || d.getKind().equals(Kind.WARNING) + ).count(), + "Should be no warn/errors"); + + // + //Test the initiation files + assertNotNull(prodInitSvs); + assertEquals(fullName(pkg, AndHowInitA_NAME), prodInitSvs.trim()); + } + + @Test + public void testServiceRegistrationOfOneTestInit() throws Exception { + + List options=new ArrayList(); + //options.add("-verbose"); + + sources.add(buildTestSource(pkg, AndHowTestInitAbstract_NAME)); + sources.add(buildTestSource(pkg, AndHowTestInitA_NAME)); + + JavaCompiler.CompilationTask task = compiler.getTask(null, manager, diagnostics, options, null, sources); + task.setProcessors(Collections.singleton(new AndHowCompileProcessor())); + task.call(); + + String testInitSvs = IOUtil.toString(loader.getResourceAsStream(TEST_INIT_SVS_PATH), Charset.forName("UTF-8")); + + assertEquals(0, + diagnostics.getDiagnostics().stream().filter( + d -> d.getKind().equals(Kind.ERROR) || d.getKind().equals(Kind.WARNING) + ).count(), + "Should be no warn/errors"); + + + // + //Test the initiation files + assertNotNull(testInitSvs); + assertEquals(fullName(pkg, AndHowTestInitA_NAME), testInitSvs.trim()); + } + + + @Test + public void testServiceRegistrationOfAndHowInitWithTooManyProdInstances() throws Exception { + + try { + + List options=new ArrayList(); + //options.add("-verbose"); + + sources.add(buildTestSource(pkg, AndHowInitAbstract_NAME)); + sources.add(buildTestSource(pkg, AndHowInitA_NAME)); + sources.add(buildTestSource(pkg, AndHowInitB_NAME)); + + JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, sources); + task.setProcessors(Collections.singleton(new AndHowCompileProcessor())); + task.call(); + + fail("Should have thrown an exception"); + + } catch (RuntimeException e) { + + assertNotNull(e.getCause()); + assertTrue(e.getCause() instanceof AndHowCompileException); + + AndHowCompileException ce = (AndHowCompileException) e.getCause(); + + assertEquals(1, ce.getProblems().size()); + assertTrue(ce.getProblems().get(0) instanceof TooManyInitClasses); + + TooManyInitClasses tmi = (TooManyInitClasses) ce.getProblems().get(0); + + assertEquals(2, tmi.getInstanceNames().size()); + assertTrue(tmi.getInstanceNames().contains(fullName(pkg, AndHowInitA_NAME))); + assertTrue(tmi.getInstanceNames().contains(fullName(pkg, AndHowInitB_NAME))); + } + } + + + @Test + public void testServiceRegistrationOfAndHowInitWithTooManyTestInstances() throws Exception { + + try { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + final MemoryFileManager manager = new MemoryFileManager(compiler); + TestClassLoader loader = new TestClassLoader(manager); + + List options=new ArrayList(); + //options.add("-verbose"); + + sources.add(buildTestSource(pkg, AndHowTestInitAbstract_NAME)); + sources.add(buildTestSource(pkg, AndHowTestInitA_NAME)); + sources.add(buildTestSource(pkg, AndHowTestInitB_NAME)); + + JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, sources); + task.setProcessors(Collections.singleton(new AndHowCompileProcessor())); + task.call(); + + fail("Should have thrown an exception"); + + } catch (RuntimeException e) { + + assertNotNull(e.getCause()); + assertTrue(e.getCause() instanceof AndHowCompileException); + + AndHowCompileException ce = (AndHowCompileException) e.getCause(); + + assertEquals(1, ce.getProblems().size()); + assertTrue(ce.getProblems().get(0) instanceof TooManyInitClasses); + + TooManyInitClasses tmi = (TooManyInitClasses) ce.getProblems().get(0); + + assertEquals(2, tmi.getInstanceNames().size()); + assertTrue(tmi.getInstanceNames().contains(fullName(pkg, AndHowTestInitA_NAME))); + assertTrue(tmi.getInstanceNames().contains(fullName(pkg, AndHowTestInitB_NAME))); + + } + } + + + @Test + public void testServiceRegistrationOfAndHowInitWithTooManyInstAndBadProperties() throws Exception { + + try { + + List options=new ArrayList(); + //options.add("-verbose"); + + sources.add(buildTestSource(pkg, AndHowInitAbstract_NAME)); + sources.add(buildTestSource(pkg, AndHowInitA_NAME)); + sources.add(buildTestSource(pkg, AndHowInitB_NAME)); + sources.add(buildTestSource(pkg, AndHowTestInitAbstract_NAME)); + sources.add(buildTestSource(pkg, AndHowTestInitA_NAME)); + sources.add(buildTestSource(pkg, AndHowTestInitB_NAME)); + sources.add(buildTestSource(pkg, "BadProps_1")); + sources.add(buildTestSource(pkg, "BadProps_2")); + + JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, sources); + task.setProcessors(Collections.singleton(new AndHowCompileProcessor())); + task.call(); + + fail("Should have thrown an exception"); + + } catch (RuntimeException e) { + + assertNotNull(e.getCause()); + assertTrue(e.getCause() instanceof AndHowCompileException); + + AndHowCompileException ace = (AndHowCompileException) e.getCause(); + + String CLASSNAME = "org.yarnandtail.andhow.compile.BadProps_1"; + String INNER_CLASSNAME = CLASSNAME + "$INNER_CLASS"; + + //Expected CompileProblems + //All problems for both classes should be reported in one exception + CompileProblem prob1 = new PropMissingFinal(CLASSNAME, "STR_1"); + CompileProblem prob2 = new PropMissingStatic(CLASSNAME, "STR_2"); + CompileProblem prob3 = new PropMissingStaticFinal(CLASSNAME, "STR_3"); + CompileProblem prob4 = new PropMissingFinal(INNER_CLASSNAME, "STR_1"); + CompileProblem prob5 = new PropMissingStatic(INNER_CLASSNAME, "STR_2"); + CompileProblem prob6 = new PropMissingStaticFinal(INNER_CLASSNAME, "STR_3"); + + //In 2nd class + CompileProblem prob7 = new PropMissingFinal( + "org.yarnandtail.andhow.compile.BadProps_2", "STR_1"); + + + assertEquals(9, ace.getProblems().size()); + assertEquals(2, + ace.getProblems().stream().filter(i -> i instanceof TooManyInitClasses).count() + ); + assertTrue(ace.getProblems().contains(prob1)); + assertTrue(ace.getProblems().contains(prob2)); + assertTrue(ace.getProblems().contains(prob3)); + assertTrue(ace.getProblems().contains(prob4)); + assertTrue(ace.getProblems().contains(prob5)); + assertTrue(ace.getProblems().contains(prob6)); + assertTrue(ace.getProblems().contains(prob7)); + } + } + +} diff --git a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/AndHowCompileProcessor_PropertyTest.java b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/AndHowCompileProcessor_PropertyTest.java new file mode 100644 index 00000000..79f488d9 --- /dev/null +++ b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/AndHowCompileProcessor_PropertyTest.java @@ -0,0 +1,178 @@ +package org.yarnandtail.andhow.compile; + +import java.nio.charset.Charset; +import java.util.*; +import javax.tools.*; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; +import org.yarnandtail.andhow.compile.CompileProblem.PropMissingFinal; +import org.yarnandtail.andhow.compile.CompileProblem.PropMissingStatic; +import org.yarnandtail.andhow.compile.CompileProblem.PropMissingStaticFinal; +import org.yarnandtail.andhow.service.*; +import org.yarnandtail.andhow.util.IOUtil; +import org.yarnandtail.compile.AndHowCompileProcessorTestBase; + +/** + * A lot of this code was borrowed from here: + * https://gist.github.com/johncarl81/46306590cbdde5a3003f + * @author ericeverman + */ +public class AndHowCompileProcessor_PropertyTest extends AndHowCompileProcessorTestBase { + + /** Shortcut to this package */ + static final String pkg = AndHowCompileProcessor_PropertyTest.class.getPackage().getName(); + + + + @Test + public void testComplexNestedPropertySampleClass() throws Exception { + + String classSimpleName = "PropertySample"; + + List options=new ArrayList(); + //options.add("-verbose"); + + sources.add(buildTestSource(pkg, classSimpleName)); + + JavaCompiler.CompilationTask task = compiler.getTask(null, manager, diagnostics, options, null, sources); + task.setProcessors(Collections.singleton(new AndHowCompileProcessor())); + task.call(); + + Object genClass = loader.loadClass(genName(pkg, classSimpleName)).newInstance(); + String genSvsFile = IOUtil.toString( + loader.getResourceAsStream(REGISTRAR_SVS_PATH), Charset.forName("UTF-8")); + + assertEquals(0, + diagnostics.getDiagnostics().stream().filter( + d -> d.getKind().equals(Diagnostic.Kind.ERROR) || d.getKind().equals(Diagnostic.Kind.WARNING) + ).count(), + "Should be no warn/errors"); + + assertNotNull(genClass); + + PropertyRegistrar registrar = (PropertyRegistrar)genClass; + + //final String ROOT = "org.yarnandtail.andhow.compile.PropertySample"; + + assertEquals(fullName(pkg, classSimpleName), registrar.getRootCanonicalName()); + List propRegs = registrar.getRegistrationList(); + + assertEquals(fullName(pkg, classSimpleName) + ".STRING", propRegs.get(0).getCanonicalPropertyName()); + assertEquals(fullName(pkg, classSimpleName) + ".STRING_PUB", propRegs.get(1).getCanonicalPropertyName()); + assertEquals(fullName(pkg, classSimpleName) + ".PC.STRING", propRegs.get(2).getCanonicalPropertyName()); + assertEquals(fullName(pkg, classSimpleName) + ".PC.STRING_PUB", propRegs.get(3).getCanonicalPropertyName()); + assertEquals(fullName(pkg, classSimpleName) + ".PC.PC_PC.STRING", propRegs.get(4).getCanonicalPropertyName()); + assertEquals(fullName(pkg, classSimpleName) + ".PC.PC_PI.STRING", propRegs.get(5).getCanonicalPropertyName()); + + assertEquals(fullName(pkg, classSimpleName) + ".PI.STRING", propRegs.get(6).getCanonicalPropertyName()); + assertEquals(fullName(pkg, classSimpleName) + ".PI.PI_DC.STRING", propRegs.get(7).getCanonicalPropertyName()); + assertEquals(fullName(pkg, classSimpleName) + ".PI.PI_DC.STRING_PUB", propRegs.get(8).getCanonicalPropertyName()); + assertEquals(fullName(pkg, classSimpleName) + ".PI.PI_DI.STRING", propRegs.get(9).getCanonicalPropertyName()); + assertEquals(fullName(pkg, classSimpleName) + ".PI.PI_DI.STRING_PUB", propRegs.get(10).getCanonicalPropertyName()); + + // + //Test the registration file + assertNotNull(genSvsFile); + assertEquals(genName(pkg, classSimpleName), genSvsFile.trim()); + } + + + @Test + public void testSimpleHappyPathClass() throws Exception { + + String classSimpleName = "HappyPathProps"; + + List options=new ArrayList(); + //options.add("-verbose"); + + sources.add(buildTestSource(pkg, classSimpleName)); + + JavaCompiler.CompilationTask task = compiler.getTask(null, manager, diagnostics, options, null, sources); + task.setProcessors(Collections.singleton(new AndHowCompileProcessor())); + task.call(); + + Object genClass = loader.loadClass(genName(pkg, classSimpleName)).newInstance(); + String genSvsFile = IOUtil.toString( + loader.getResourceAsStream(REGISTRAR_SVS_PATH), Charset.forName("UTF-8")); + + assertEquals(0, + diagnostics.getDiagnostics().stream().filter( + d -> d.getKind().equals(Diagnostic.Kind.ERROR) || d.getKind().equals(Diagnostic.Kind.WARNING) + ).count(), + "Should be no warn/errors"); + + assertNotNull(genClass); + + PropertyRegistrar registrar = (PropertyRegistrar)genClass; + + //final String ROOT = "org.yarnandtail.andhow.compile.PropertySample"; + + assertEquals(fullName(pkg, classSimpleName), registrar.getRootCanonicalName()); + List propRegs = registrar.getRegistrationList(); + + assertEquals(fullName(pkg, classSimpleName) + ".STR_1", propRegs.get(0).getCanonicalPropertyName()); + assertEquals(fullName(pkg, classSimpleName) + ".STR_2", propRegs.get(1).getCanonicalPropertyName()); + assertEquals(fullName(pkg, classSimpleName) + ".INNER.STR_1", propRegs.get(2).getCanonicalPropertyName()); + assertEquals(fullName(pkg, classSimpleName) + ".INNER.STR_2", propRegs.get(3).getCanonicalPropertyName()); + + // + //Test the registration file + assertNotNull(genSvsFile); + assertEquals(genName(pkg, classSimpleName), genSvsFile.trim()); + } + + + @Test + public void testMissingStaticAndFinalModifiersOnProperties() throws Exception { + + List options=new ArrayList(); + //options.add("-verbose"); + + sources.add(buildTestSource(pkg, "BadProps_1")); + sources.add(buildTestSource(pkg, "BadProps_2")); + + JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, options, null, sources); + task.setProcessors(Collections.singleton(new AndHowCompileProcessor())); + + try { + task.call(); + fail("This should have thrown an exception"); + } catch (RuntimeException e) { + + assertTrue(e.getCause() instanceof AndHowCompileException); + + AndHowCompileException ace = (AndHowCompileException) e.getCause(); + + String CLASSNAME = "org.yarnandtail.andhow.compile.BadProps_1"; + String INNER_CLASSNAME = CLASSNAME + "$INNER_CLASS"; + + //Expected CompileProblems + //All problems for both classes should be reported in one exception + CompileProblem prob1 = new PropMissingFinal(CLASSNAME, "STR_1"); + CompileProblem prob2 = new PropMissingStatic(CLASSNAME, "STR_2"); + CompileProblem prob3 = new PropMissingStaticFinal(CLASSNAME, "STR_3"); + CompileProblem prob4 = new PropMissingFinal(INNER_CLASSNAME, "STR_1"); + CompileProblem prob5 = new PropMissingStatic(INNER_CLASSNAME, "STR_2"); + CompileProblem prob6 = new PropMissingStaticFinal(INNER_CLASSNAME, "STR_3"); + + //In 2nd class + CompileProblem prob7 = new PropMissingFinal( + "org.yarnandtail.andhow.compile.BadProps_2", "STR_1"); + + assertEquals(7, ace.getProblems().size()); + assertTrue(ace.getProblems().contains(prob1)); + assertTrue(ace.getProblems().contains(prob2)); + assertTrue(ace.getProblems().contains(prob3)); + assertTrue(ace.getProblems().contains(prob4)); + assertTrue(ace.getProblems().contains(prob5)); + assertTrue(ace.getProblems().contains(prob6)); + assertTrue(ace.getProblems().contains(prob7)); + + } catch (Throwable t) { + fail("This should have thrown an AndHowCompileException"); + } + + } + + +} diff --git a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/CompileProblemTest.java b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/CompileProblemTest.java new file mode 100644 index 00000000..2fa41e18 --- /dev/null +++ b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/CompileProblemTest.java @@ -0,0 +1,135 @@ +/* + */ +package org.yarnandtail.andhow.compile; + +import java.util.ArrayList; +import javax.lang.model.element.Element; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.yarnandtail.andhow.compile.CompileProblem.*; +import static org.mockito.Mockito.*; +import org.yarnandtail.andhow.compile.AndHowCompileProcessor.CauseEffect; + +/** + * + * @author ericeverman + */ +public class CompileProblemTest { + + public CompileProblemTest() { + } + + + @Test + public void testBasicPropsOfPropMissingStatic() { + PropMissingStatic prob1 = new PropMissingStatic("g1", "p1"); + PropMissingStatic prob2 = new PropMissingStatic("g1", "p1"); + PropMissingStatic prob3 = new PropMissingStatic("Xg1", "Xp1"); + + assertTrue(prob1.isPropertyProblem()); + assertEquals("g1", prob1.getGroupName()); + assertEquals("p1", prob1.getPropertyName()); + assertTrue(prob1.getFullMessage().contains("g1")); + assertTrue(prob1.getFullMessage().contains("p1")); + + assertEquals(prob1, prob2); + assertNotEquals(prob1, prob3); + assertNotEquals(prob2, prob3); + } + + @Test + public void testBasicPropsOfPropMissingFinal() { + PropMissingFinal prob1 = new PropMissingFinal("g1", "p1"); + PropMissingFinal prob2 = new PropMissingFinal("g1", "p1"); + PropMissingFinal prob3 = new PropMissingFinal("Xg1", "Xp1"); + + assertTrue(prob1.isPropertyProblem()); + assertEquals("g1", prob1.getGroupName()); + assertEquals("p1", prob1.getPropertyName()); + assertTrue(prob1.getFullMessage().contains("g1")); + assertTrue(prob1.getFullMessage().contains("p1")); + + assertEquals(prob1, prob2); + assertNotEquals(prob1, prob3); + assertNotEquals(prob2, prob3); + } + + @Test + public void testBasicPropsOfPropMissingStaticFinal() { + PropMissingStaticFinal prob1 = new PropMissingStaticFinal("g1", "p1"); + PropMissingStaticFinal prob2 = new PropMissingStaticFinal("g1", "p1"); + PropMissingStaticFinal prob3 = new PropMissingStaticFinal("Xg1", "Xp1"); + + assertTrue(prob1.isPropertyProblem()); + assertEquals("g1", prob1.getGroupName()); + assertEquals("p1", prob1.getPropertyName()); + assertTrue(prob1.getFullMessage().contains("g1")); + assertTrue(prob1.getFullMessage().contains("p1")); + + assertEquals(prob1, prob2); + assertNotEquals(prob1, prob3); + assertNotEquals(prob2, prob3); + } + + @Test + public void testTooManyInitClasses() { + Element element1 = mock(Element.class); + Element element2 = mock(Element.class); + + CauseEffect ce1 = new CauseEffect("org.MyClass1", element1); + CauseEffect ce2 = new CauseEffect("org.MyClass2", element2); + ArrayList ces1 = new ArrayList(); + ces1.add(ce1); + ces1.add(ce2); + + final String INIT_NAME = "SOME_INTERFACE_NAME"; + + TooManyInitClasses tmi1a = new TooManyInitClasses(INIT_NAME, ces1); + TooManyInitClasses tmi1null = new TooManyInitClasses(INIT_NAME, null); + + assertFalse(tmi1a.isPropertyProblem()); + assertEquals(INIT_NAME, tmi1a.getInitClassName()); + assertTrue(tmi1a.getFullMessage().contains(INIT_NAME)); + assertTrue(tmi1a.getFullMessage().contains(ce1.fullClassName)); + assertTrue(tmi1a.getFullMessage().contains(ce2.fullClassName)); + assertEquals(2, tmi1a.getInstanceNames().size()); + assertTrue(tmi1a.getInstanceNames().contains(ce1.fullClassName)); + assertTrue(tmi1a.getInstanceNames().contains(ce2.fullClassName)); + assertEquals(0, tmi1null.getInstanceNames().size()); + + TooManyInitClasses tmi1b = new TooManyInitClasses(INIT_NAME, ces1); + + ArrayList ces2 = new ArrayList(); + ces2.add(ce1); + TooManyInitClasses tmi2 = new TooManyInitClasses(INIT_NAME, ces2); + + assertEquals(tmi1a, tmi1b); + assertEquals(tmi1b, tmi1a); + assertNotEquals(tmi1a, tmi1null); + assertNotEquals(tmi1a, null); + assertNotEquals(tmi1a, new PropMissingFinal("g1", "p1")); + assertNotEquals(tmi1a, tmi2); + assertNotEquals(tmi2, tmi1a); + } + + + @Test + public void testEqualsAcrossTypes() { + Integer i1 = 1; + CompileProblem prob1 = new PropMissingStatic("g1", "p1"); + CompileProblem prob2 = new PropMissingFinal("g1", "p1"); + CompileProblem prob3 = new PropMissingStaticFinal("g1", "p1"); + + //Just checking + assertEquals(prob1, prob1); + assertEquals(prob2, prob2); + assertEquals(prob3, prob3); + + assertNotEquals(prob1, null); + assertNotEquals(prob1, i1); + assertNotEquals(prob1, prob2); + assertNotEquals(prob1, prob3); + assertNotEquals(prob2, prob3); + } + +} diff --git a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/CompileUnitTest.java b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/CompileUnitTest.java index 2e474f9a..cbde599d 100644 --- a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/CompileUnitTest.java +++ b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/CompileUnitTest.java @@ -1,9 +1,9 @@ package org.yarnandtail.andhow.compile; import org.yarnandtail.andhow.service.PropertyRegistrationList; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * @@ -32,12 +32,12 @@ public void testHappyPath() { CompileUnit cu = new CompileUnit(ROOT_QUAL_NAME); //root prop - cu.addProperty(new SimpleVariable(PROP1_NAME, true, true)); //0 + cu.addProperty(PROP1_NAME, true, true); //0 { //1st inner class cu.pushType(INNER1_SIMP_NAME, true); - cu.addProperty(new SimpleVariable(PROP1_NAME, true, true)); //1 + cu.addProperty(PROP1_NAME, true, true); //1 { //2nd inner class @@ -51,8 +51,8 @@ public void testHappyPath() { assertEquals(INNER2_SIMP_NAME, cu.getInnerPathNames().get(1)); // - cu.addProperty(new SimpleVariable(PROP1_NAME, true, true)); //2 - cu.addProperty(new SimpleVariable(PROP2_NAME, true, true)); //3 + cu.addProperty(PROP1_NAME, true, true); //2 + cu.addProperty(PROP2_NAME, true, true); //3 cu.popType(); //Check we have the right inner path remaining @@ -61,12 +61,12 @@ public void testHappyPath() { } //one more at 1st inner level - cu.addProperty(new SimpleVariable(PROP2_NAME, true, true)); //4 + cu.addProperty(PROP2_NAME, true, true); //4 cu.popType(); } //one more at root level - cu.addProperty(new SimpleVariable(PROP2_NAME, true, true)); //5 + cu.addProperty(PROP2_NAME, true, true); //5 PropertyRegistrationList list = cu.getRegistrations(); diff --git a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/CompileUtilTest.java b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/CompileUtilTest.java new file mode 100644 index 00000000..4d0c64bf --- /dev/null +++ b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/CompileUtilTest.java @@ -0,0 +1,47 @@ +package org.yarnandtail.andhow.compile; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class CompileUtilTest { + + @Test + public void getGeneratedAnnotationClassNameTest() { + assertEquals("javax.annotation.Generated", CompileUtil.getGeneratedAnnotationClassName(8)); + assertEquals("javax.annotation.processing.Generated", CompileUtil.getGeneratedAnnotationClassName(9)); + } + + @Test + public void getMajorJavaVersionHappyTest() { + + assertEquals(8, CompileUtil.getMajorJavaVersion("1.8")); + assertEquals(8, CompileUtil.getMajorJavaVersion("1.8.434-be")); + assertEquals(8, CompileUtil.getMajorJavaVersion("1.8.0_152")); + + assertEquals(9, CompileUtil.getMajorJavaVersion("9")); + assertEquals(9, CompileUtil.getMajorJavaVersion("9.0.1")); + + //Weird stuff still ok + assertEquals(9, CompileUtil.getMajorJavaVersion("9-prerelease")); + assertEquals(9, CompileUtil.getMajorJavaVersion("9_prerelease")); + + assertEquals(11, CompileUtil.getMajorJavaVersion("11")); + assertEquals(11, CompileUtil.getMajorJavaVersion("11.0")); + assertEquals(11, CompileUtil.getMajorJavaVersion("11.1")); + assertEquals(11, CompileUtil.getMajorJavaVersion("11.0.1")); + assertEquals(11, CompileUtil.getMajorJavaVersion("11.1-b53")); + } + + @Test + public void getMajorJavaVersionExceptionTest() { + + try { + CompileUtil.getMajorJavaVersion("WeirdVersion"); + fail("This should have thrown an exception"); + } catch (Exception e) { + //Expected + } + + } +} \ No newline at end of file diff --git a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/PropertyRegistrarClassGeneratorTest.java b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/PropertyRegistrarClassGeneratorTest.java index fb8cf10b..9f845aa3 100644 --- a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/PropertyRegistrarClassGeneratorTest.java +++ b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/PropertyRegistrarClassGeneratorTest.java @@ -3,13 +3,13 @@ import com.google.testing.compile.Compilation; import com.google.testing.compile.JavaFileObjects; import java.util.*; -import javax.tools.JavaFileObject; -import org.junit.Test; -import org.junit.Before; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import static com.google.testing.compile.CompilationSubject.assertThat; import static com.google.testing.compile.Compiler.javac; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * @@ -28,7 +28,7 @@ public class PropertyRegistrarClassGeneratorTest { private GregorianCalendar runDate; - @Before + @BeforeEach public void initEach() { runDate = new GregorianCalendar(); //runDate.setTimeZone(TimeZone.getTimeZone(ZoneId.of("GMT-6"))); @@ -143,7 +143,7 @@ public void testBuildRegistrationAddsString_Complex() { assertEquals("list.add(\"" + PROP1_NAME + "\", \"" + INNER1_SIMP_NAME + "\", \"" + INNER2_SIMP_NAME + "\");", eachAdds[3]); assertEquals("list.add(\"" + PROP2_NAME + "\");", eachAdds[4]); //No inner path b/c in inherits from above } - + /** * Basic gross test that the generated source is compilable */ @@ -160,6 +160,17 @@ public void testGenerate_simple() throws Exception { assertThat(compilation).succeeded(); + //Check the source file + + //This method is separately tested, so lets trust it works correctly + int javaVersion = CompileUtil.getMajorJavaVersion(System.getProperty("java.version")); + + if (javaVersion < 9) { + assertTrue(sourceStr.contains("@javax.annotation.Generated(")); + } else { + assertTrue(sourceStr.contains("@javax.annotation.processing.Generated(")); + } + } /** @@ -204,7 +215,7 @@ public CompileUnit simpleCompileUnit() { CompileUnit cu = new CompileUnit(ROOT_QUAL_NAME); //root prop - cu.addProperty(new SimpleVariable(PROP1_NAME, true, true)); + cu.addProperty(PROP1_NAME, true, true); cu.addProperty(PROP2_NAME, true, true); return cu; @@ -215,7 +226,7 @@ public CompileUnit simpleCompileUnit_DefaultPkg() { CompileUnit cu = new CompileUnit(ROOT_SIMPLE_NAME); //root prop - cu.addProperty(new SimpleVariable(PROP1_NAME, true, true)); + cu.addProperty(PROP1_NAME, true, true); cu.addProperty(PROP2_NAME, true, true); return cu; @@ -229,18 +240,18 @@ public CompileUnit complexCompileUnit() { CompileUnit cu = new CompileUnit(ROOT_QUAL_NAME); //root prop - cu.addProperty(new SimpleVariable(PROP1_NAME, true, true)); + cu.addProperty(PROP1_NAME, true, true); { //1st inner class cu.pushType(INNER1_SIMP_NAME, true); - cu.addProperty(new SimpleVariable(PROP1_NAME, true, true)); + cu.addProperty(PROP1_NAME, true, true); { //2nd inner class cu.pushType(INNER2_SIMP_NAME, true); - cu.addProperty(new SimpleVariable(PROP1_NAME, true, true)); - cu.addProperty(new SimpleVariable(PROP2_NAME, true, true)); + cu.addProperty(PROP1_NAME, true, true); + cu.addProperty(PROP2_NAME, true, true); cu.popType(); } diff --git a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/__PropertySample_PropertyRegistrationForClassTest.java b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/__PropertySample_PropertyRegistrationForClassTest.java index af159b8b..ca8008e8 100644 --- a/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/__PropertySample_PropertyRegistrationForClassTest.java +++ b/andhow-annotation-processor/src/test/java/org/yarnandtail/andhow/compile/__PropertySample_PropertyRegistrationForClassTest.java @@ -3,9 +3,9 @@ import org.yarnandtail.andhow.service.PropertyRegistrar; import org.yarnandtail.andhow.service.PropertyRegistration; import java.util.List; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * diff --git a/andhow-annotation-processor/src/test/resources/org/yarnandtail/andhow/compile/BadProps_1.java b/andhow-annotation-processor/src/test/resources/org/yarnandtail/andhow/compile/BadProps_1.java new file mode 100644 index 00000000..56cc8efa --- /dev/null +++ b/andhow-annotation-processor/src/test/resources/org/yarnandtail/andhow/compile/BadProps_1.java @@ -0,0 +1,27 @@ +package org.yarnandtail.andhow.compile; + +import org.yarnandtail.andhow.property.StrProp; + +/** + * A class that should have compile problems + * + * @author ericeverman + */ +public class BadProps_1 { + + private static StrProp STR_1 = StrProp.builder().build(); //---Should be final!!! + public final StrProp STR_2 = StrProp.builder().build(); //---Should be static!!! + StrProp STR_3 = StrProp.builder().build(); //---Should be static final!!! + + private static interface INNER { + StrProp STR_1 = StrProp.builder().build(); //static final is assumed + static final StrProp STR_2 = StrProp.builder().build(); + } + + private static class INNER_CLASS { + private static StrProp STR_1 = StrProp.builder().build(); //---Should be final!!! + public final StrProp STR_2 = StrProp.builder().build(); //---Should be static!!! + StrProp STR_3 = StrProp.builder().build(); //---Should be static final!!! + } + +} diff --git a/andhow-annotation-processor/src/test/resources/org/yarnandtail/andhow/compile/BadProps_2.java b/andhow-annotation-processor/src/test/resources/org/yarnandtail/andhow/compile/BadProps_2.java new file mode 100644 index 00000000..0fe8e6fa --- /dev/null +++ b/andhow-annotation-processor/src/test/resources/org/yarnandtail/andhow/compile/BadProps_2.java @@ -0,0 +1,14 @@ +package org.yarnandtail.andhow.compile; + +import org.yarnandtail.andhow.property.StrProp; + +/** + * A class that should have compile problems + * + * @author ericeverman + */ +public class BadProps_2 { + + private static StrProp STR_1 = StrProp.builder().build(); //---Should be final!!! + +} diff --git a/andhow-annotation-processor/src/test/resources/org/yarnandtail/andhow/compile/HappyPathProps.java b/andhow-annotation-processor/src/test/resources/org/yarnandtail/andhow/compile/HappyPathProps.java new file mode 100644 index 00000000..c8c4349a --- /dev/null +++ b/andhow-annotation-processor/src/test/resources/org/yarnandtail/andhow/compile/HappyPathProps.java @@ -0,0 +1,21 @@ +package org.yarnandtail.andhow.compile; + +import org.yarnandtail.andhow.property.StrProp; + +/** + * A class that should compile just fine w/ AndHow's Annotation compiler + * + * @author ericeverman + */ +public class HappyPathProps { + + private static final StrProp STR_1 = StrProp.builder().build(); + public static final StrProp STR_2 = StrProp.builder().build(); + + private static interface INNER { + + static final StrProp STR_1 = StrProp.builder().build(); + static final StrProp STR_2 = StrProp.builder().build(); + } + +} diff --git a/andhow-core/pom.xml b/andhow-core/pom.xml index 74647c66..7dd5188b 100644 --- a/andhow-core/pom.xml +++ b/andhow-core/pom.xml @@ -3,7 +3,7 @@ org.yarnandtail andhow-parent - 0.4.1-SNAPSHOT + 0.4.2-SNAPSHOT andhow-core @@ -14,12 +14,7 @@ - - - junit - junit - org.springframework spring-test @@ -29,5 +24,4 @@ commons-io - diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/AndHow.java b/andhow-core/src/main/java/org/yarnandtail/andhow/AndHow.java index 9de90cbd..ed1390cb 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/AndHow.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/AndHow.java @@ -2,8 +2,10 @@ import java.lang.reflect.Field; import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; import org.yarnandtail.andhow.api.*; import org.yarnandtail.andhow.internal.AndHowCore; +import org.yarnandtail.andhow.internal.ConstructionProblem; import org.yarnandtail.andhow.util.AndHowUtil; /** @@ -41,9 +43,19 @@ public class AndHow implements StaticPropertyConfiguration, ValidatedValues { private static final Object LOCK = new Object(); private volatile AndHowCore core; + + /** Stack trace and time of startup */ + private static volatile Initialization initialization; + + /** + * True only during the instance(AndHowConfiguration) method to detect + * re-entrant initialization + */ + private static AtomicBoolean initializing = new AtomicBoolean(false); private AndHow(AndHowConfiguration config) throws AppFatalException { synchronized (LOCK) { + core = new AndHowCore( config.getNamingStrategy(), config.buildLoaders(), @@ -117,34 +129,67 @@ public static AndHow instance(AndHowConfiguration config) throws AppFatalExcepti } else { if (singleInstance == null) { - - singleInstance = new AndHow(config); - + + if (! initializing.get()) { + + try { + + initializing.getAndSet(true); //Block re-entrant initialization + initialization = new Initialization(); //Record initialization time & place + singleInstance = new AndHow(config); //Build new instance + + } finally { + initializing.getAndSet(false); //Done w/ init regardless of possible error + } + + } else { + + throw new AppFatalException( + new ConstructionProblem.InitiationLoopException(initialization, new Initialization())); + } + } else if (singleInstance.core == null) { /* This is a concession for testing. During testing the core is deleted to force AndHow to reload. Its really an invalid state (instance and core should be null/non-null together, but its handled here to simplify testing. */ - try { - - AndHowCore newCore = new AndHowCore( - config.getNamingStrategy(), - config.buildLoaders(), - config.getRegisteredGroups()); - Field coreField = AndHow.class.getDeclaredField("core"); - coreField.setAccessible(true); - coreField.set(singleInstance, newCore); - - } catch (Exception ex) { - if (ex instanceof AppFatalException) { - throw (AppFatalException) ex; - } else { - throwFatal("", ex); + + if (! initializing.get()) { + + try { + + initializing.getAndSet(true); //Block re-entrant initialization + initialization = new Initialization(); //Record initialization time & place + + AndHowCore newCore = new AndHowCore( + config.getNamingStrategy(), + config.buildLoaders(), + config.getRegisteredGroups()); + Field coreField = AndHow.class.getDeclaredField("core"); + coreField.setAccessible(true); + coreField.set(singleInstance, newCore); + + } catch (Exception ex) { + + if (ex instanceof AppFatalException) { + throw (AppFatalException) ex; + } else { + throwFatal("", ex); + } + } finally { + initializing.getAndSet(false); //Done w/ init regardless of possible error } + + } else { + + throw new AppFatalException( + new ConstructionProblem.InitiationLoopException(initialization, new Initialization())); + } } + return singleInstance; } @@ -160,6 +205,28 @@ invalid state (instance and core should be null/non-null public static boolean isInitialize() { return singleInstance != null && singleInstance.core != null; } + + /** + * Get the stacktrace of where AndHow was initialized. + * + * This can be useful for debugging InitiationLoopException errors or + * errors caused by trying to initialize AndHow when it is already initialized. + * This stacktrace identifies the point in code that caused the initial + * AndHow initialization, prior to the error. The reported exception will + * point to the place where AndHow entered a loop during its construction + * or application code attempted to re-initialize AndHow. + * + * @return A stacktrace if it is available (some JVMs may not provide one) + * or an empty stacktrace array if it is not available or AndHow is not + * yet initialized. + */ + public static StackTraceElement[] getInitializationTrace() { + if (initialization != null) { + return initialization.getStackTrace(); + } else { + return new StackTraceElement[0]; + } + } // //PropertyValues Interface @@ -219,5 +286,31 @@ private static void throwFatal(String message, Throwable throwable) { throw afe; } } + + + /** + * Encapsilate when and where AndHow was initialized. + * + * Useful for debugging re-entrant startups or uncontrolled startup conditions. + */ + public static class Initialization { + private StackTraceElement[] stackTrace; + private long timeStamp; + + public Initialization() { + timeStamp = System.currentTimeMillis(); + StackTraceElement[] ste = new Exception().getStackTrace(); + stackTrace = Arrays.copyOfRange(ste, 1, ste.length - 1); + } + + public StackTraceElement[] getStackTrace() { + return stackTrace; + } + + public long getTimeStamp() { + return timeStamp; + } + + } } diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/AndHowConfiguration.java b/andhow-core/src/main/java/org/yarnandtail/andhow/AndHowConfiguration.java index c8dfa6dc..8c74ddee 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/AndHowConfiguration.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/AndHowConfiguration.java @@ -2,6 +2,7 @@ import java.util.List; import org.yarnandtail.andhow.api.*; +import org.yarnandtail.andhow.property.StrProp; /** * @@ -30,6 +31,10 @@ public interface AndHowConfiguration { * prior to any other loader. Since the first loaded value for a Property * 'wins', this effectively fixes the value and makes it non-configurable. * + * Values specified by the two addFixedValue methods will + * through a DuplicatePropertyLoaderProblem if they refer to + * the same Property. + * * @param The type of Property and value * @param property The property to set a value for * @param value The value to set. @@ -38,15 +43,216 @@ public interface AndHowConfiguration { C addFixedValue(Property property, T value); /** - * Removes a PropertyValue from the list of fixed values. + * Removes a Property value set only via addFixedValue(Property, T value) + * from the list of fixed values. * - * It is not an error to attempt to remove a property that is not in the - * current fixed value list. + * It is not an error to attempt to remove a property that is not in this fixed value list. * * @param property A non-null property. * @return */ C removeFixedValue(Property property); - + + /** + * Sets a fixed, non-configurable value for a named Property. + * + * Property values set in this way use the FixedValueLoader to load values + * prior to any other loader. Since the first loaded value for a Property + * 'wins', this effectively fixes the value and makes it non-configurable. + * + * Values specified by the two addFixedValue methods will + * through a DuplicatePropertyLoaderProblem if they refer to + * the same Property. + * + * @param name The canonical or alias name of Property, which is trimmed to null. + * @param value The Object value to set, which must match the type of the Property. + * @return + */ + C addFixedValue(String name, Object value); + + /** + * Removes a Property value set only via addFixedValue(String name, Object value) + * from the list of fixed values. + * + * Note that to successfully remove a fixed value from this list, the name must exactly + * match the name used to set the property via addFixedValue(String, Object). Since + * Properties can have aliases, you must know the exact name to set the property. + * + * It is not an error to attempt to remove a property that is not in this fixed value list, + * or to attempt to remove a property value that does not exist - these are just no-ops. + * + * @param propertyNameOrAlias The name or alias of a property. + * @return + */ + C removeFixedValue(String propertyNameOrAlias); + + /** + * Sets the classpath path to a properties file for the + * {@Code StdPropFileOnClasspathLoader} to read and load from. + * + * If no path is specified via one of the two {@Code setClasspathPropFilePath} methods, + * the default classpath of '/andhow.properties' is used. + *

+ * As per Java convention, a path on the classpath can use dots or slashes to separate packages. + * However, if the file name itself contains dots, then the path must start with a slash and use + * slashes to separate packages. + *

+ * Valid Examples: + *

    + *
  • /andhow.property - The default. The file is at the root and the name contains a dot
  • + *
  • /org/ngo/config.props - Similar to above, but in the org.ngo package, the file name is 'config.props'
  • + *
  • org.ngo.props - The package is org.ngo, the file name is 'props'. + * There are no dots in the file name, so its OK to use dots to separate the packages
  • + *
+ * + * @param classpathPropFilePathString + * @return + */ + C setClasspathPropFilePath(String classpathPropFilePathString); + + /** + * Sets the classpath path via a StrProp (a Property of String type) to a + * properties file for the {@Code StdPropFileOnClasspathLoader} to read and load from. + * + * If no path is specified via one of the two {@Code setClasspathPropFilePath} methods, + * the default classpath of '/andhow.properties' is used. + *

+ * As per Java convention, a path on the classpath can use dots or slashes to separate packages. + * However, if the file name itself contains dots, then the path must start with a slash and use + * slashes to separate packages. Its common to have a '.properties' extension on the properties + * file, so its good practice to add a validation rule to the StrProp used here to ensure it + * {@Code mustStartWith("/")}. + *

+ * Valid Examples of configured values: + *

    + *
  • /andhow.property - The default. The file is at the root and the name contains a dot
  • + *
  • /org/ngo/config.props - Similar to above, but in the org.ngo package, the file name is 'config.props'
  • + *
  • org.ngo.props - The package is org.ngo, the file name is 'props'. + * There are no dots in the file name, so its OK to use dots to separate the packages
  • + *
+ * + * @param classpathPropFilePathProperty + * @return + */ + C setClasspathPropFilePath(StrProp classpathPropFilePathProperty); + + /** + * If called to set this to 'required', a classpath properties file must + * exist and be readable. This flag is used by the {@Code StdPropFileOnClasspathLoader}. + * + * Since the {@Code StdPropFileOnClasspathLoader} has a default property file name, + * {@Code /andhow.properties}, setting this to 'required' means that either that + * default file name or another that you configure instead must exist. + * + * @See setClasspathPropFilePath methods for details on using a non-default + * classpath properties file. + * + * A RuntimeException will be thrown if this is set to 'required' and there + * is no classpath properties file that can be read. + *
+ * This is NOT set by default, allowing the properties file to be optional. + * + * @return + */ + C classpathPropertiesRequired(); + + /** + * Sets the properties file on the classpath to be optional, the default. + * + * @See classpathPropertiesRequired + * + * @return + */ + C classpathPropertiesNotRequired(); + + /** + * Sets the filesystem path via a StrProp (a Property of String type) to a + * properties file for the StdPropFileOnFilesystemLoader to load. + * + * If no property is set to specify a path, or a property is set by has no + * value, this loader won't be used. If the property is specified but the + * specified file is missing, an error will be thrown based on the + * filesystemPropFileRequired flag. + * + * Paths should generally be absolute and correctly formed for the host + * environment. + * + * @param filesystemPropFilePath + * @return + */ + C setFilesystemPropFilePath(StrProp filesystemPropFilePath); + + /** + * If called to set this to 'required', a non-null configured value for the + * filesystem properties file must point to an existing, readable properties + * file. This flag is used by the {@Code StdPropFileOnFilesystemLoader}. + * + * A RuntimeException will be thrown if this is set to 'required' and there + * is a path specified which points to a file that does not exist. + * Configuring a filesystem path is a two step process:
    + *
  • First, a StrProp Property must be specified for this configuration + * via the {@Code setFilesystemPropFilePath} method
  • + *
  • Then, a value must be configured for in an any way that AndHow + * reads and loads values, such as environment vars, system properties, etc..
  • + *
+ * If and non-null value is configured, its doesn't point to a readable properties + * file, AND this required flag is set, a RuntimeException will be thrown at startup. + *
+ * This is NOT set by default, allowing the properties file to be optional. + * + * @return + */ + C filesystemPropFileRequired(); + + /** + * Sets the properties file on the filesystem to be optional, the default. + * + * @See setFilesystemPropFilePath + * + * @return + */ + C filesystemPropFileNotRequired(); + + // + //Loader related + + /** + * The default list of standard loaders, as a list of Classes that implement + * {@Code StandardLoader} + *

+ * The returned list is disconnected from the actual list of loaders - it is + * intended to be a starting point for applications that want to modify the + * list, then call setStandardLoaders(). + *

+ * Unlike other methods of this class, it does not fluently return a method + * to itself, so your code will need a AndHowConfiguration instance reference to + * use it, eg: + *

{@Code
+	 * public class MyAppInitiation implements AndHowInit {
+	 *    @Override
+	 *  	public AndHowConfiguration getConfiguration() {
+	 * 			AndHowConfiguration config = AndHow.findConfig();
+	 * 			List> sll = config.getDefaultLoaderList();
+	 * 			...do some rearranging of the list...
+	 *
+	 * 			config.setStandardLoaders(sll) ...and go on to call other methods on config...
+	 * 		}
+	 * }
+	 * }
+ *

+ * Note: AndHow version up to and including 0.4.1 had this method as a static + * method. + * @return + */ + List> getDefaultLoaderList(); + + C setStandardLoaders(List> newStandardLoaders); + + C setStandardLoaders(Class... newStandardLoaders); + + C insertLoaderBefore(Class insertBeforeThisLoader, Loader loaderToInsert); + + C insertLoaderAfter(Class insertAfterThisLoader, Loader loaderToInsert); + void build(); } diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/BaseConfig.java b/andhow-core/src/main/java/org/yarnandtail/andhow/BaseConfig.java index 227fbf81..92600621 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/BaseConfig.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/BaseConfig.java @@ -3,6 +3,7 @@ import java.lang.reflect.*; import java.util.*; import org.yarnandtail.andhow.api.*; +import org.yarnandtail.andhow.load.KeyObjectPair; import org.yarnandtail.andhow.load.std.*; import org.yarnandtail.andhow.name.CaseInsensitiveNaming; import org.yarnandtail.andhow.property.StrProp; @@ -32,9 +33,14 @@ public abstract class BaseConfig> implements AndHowConfi protected Map, List> insertBefore = new HashMap(); protected Map, List> insertAfter = new HashMap(); - //A list of hardcoded values used by the StdFixedValueLoader + //A list of hardcoded values used by the StdFixedValueLoader. + //Provided w/ live Property references protected final List _fixedVals = new ArrayList(); + //A list of hardcoded values used by the StdFixedValueLoader. + //Provided as key name (string) and value (object) + protected final List _fixedKeyObjectPairVals = new ArrayList(); + //A list of command line arguments protected final List _cmdLineArgs = new ArrayList(); @@ -71,6 +77,7 @@ public NamingStrategy getNamingStrategy() { protected StdFixedValueLoader buildStdFixedValueLoader() { StdFixedValueLoader loader = new StdFixedValueLoader(); loader.setPropertyValues(_fixedVals); + loader.setKeyObjectPairValues(_fixedKeyObjectPairVals); return loader; } @@ -162,14 +169,9 @@ public List getRegisteredGroups() { public void build() { AndHow.instance(this); } - - /** - * The list of default loaders as a list. - * - * This is a disconnected list from any instance of the BaseConfig. - * @return - */ - public static List> getDefaultLoaderList() { + + @Override + public List> getDefaultLoaderList() { List> loaders = new ArrayList(); diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/StdConfig.java b/andhow-core/src/main/java/org/yarnandtail/andhow/StdConfig.java index b5c4a10c..d3f0c53e 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/StdConfig.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/StdConfig.java @@ -2,6 +2,7 @@ import java.util.*; import org.yarnandtail.andhow.api.*; +import org.yarnandtail.andhow.load.KeyObjectPair; import org.yarnandtail.andhow.property.StrProp; import org.yarnandtail.andhow.util.TextUtil; @@ -34,6 +35,7 @@ public S addFixedValue(Property property, T value) { throw new IllegalArgumentException("The property cannot be null"); } + //simple check for duplicates doesn't consider KOP values for (PropertyValue pv : _fixedVals) { if (property.equals(pv.getProperty())) { throw new IllegalArgumentException("A fixed value for this property has been assigned twice."); @@ -48,20 +50,36 @@ public S addFixedValue(Property property, T value) { @Override public S removeFixedValue(Property property) { + _fixedVals.removeIf(f -> f.getProperty().equals(property)); + return (S) this; + } - if (property == null) { - throw new IllegalArgumentException("The property cannot be null"); - } + @Override + public S addFixedValue(final String propertyNameOrAlias, final Object value) { - Iterator it = _fixedVals.iterator(); - while (it.hasNext()) { - PropertyValue pv = it.next(); - if (property.equals(pv.getProperty())) { - it.remove(); - break; + try { + KeyObjectPair kop = new KeyObjectPair(propertyNameOrAlias, value); + + //Simple check for duplicates + if (_fixedKeyObjectPairVals.stream().map(k -> k.getName()).anyMatch(n -> n.equals(kop.getName()))) { + throw new IllegalArgumentException( + "A fixed value for the Property '" + kop.getName() + "' has been assigned twice."); } + + _fixedKeyObjectPairVals.add(kop); + + return (S) this; + + } catch (ParsingException e) { + throw new IllegalArgumentException(e); } + } + + @Override + public S removeFixedValue(final String propertyNameOrAlias) { + final String cleanName = TextUtil.trimToNull(propertyNameOrAlias); + _fixedKeyObjectPairVals.removeIf(k -> k.getName().equals(cleanName)); return (S) this; } @@ -75,20 +93,7 @@ public S setCmdLineArgs(String[] commandLineArgs) { return (S) this; } - /** - * Sets the classpath path to a properties file for the - * StdPropFileOnClasspathLoader to load. - * - * If no path is specified via either a String or StrProp, the path - * '/andhow.properties' is used.
- * - * Paths should start with a forward slash and have packages delimited by - * forward slashes. If the file name contains a dot, the path must - * start with a forward slash. - * - * @param classpathPropFilePathString - * @return - */ + @Override public S setClasspathPropFilePath(String classpathPropFilePathString) { classpathPropFilePathString = TextUtil.trimToNull(classpathPropFilePathString); @@ -98,33 +103,21 @@ public S setClasspathPropFilePath(String classpathPropFilePathString) { + "be specified as both a String and StrProp"); } - if (classpathPropFilePathString != null && !classpathPropFilePathString.startsWith("/") - && (classpathPropFilePathString.endsWith(".properties") || classpathPropFilePathString.endsWith(".xml"))) { - - throw new IllegalArgumentException("The path to the property file on " - + "the classpath should start with a '/' if the filename contains a dot."); + if ( + classpathPropFilePathString != null && + classpathPropFilePathString.contains(".") && + !classpathPropFilePathString.startsWith("/") + ) { + throw new IllegalArgumentException("A path to a property file on the classpath " + + "must start with a '/' if the filename contains a dot."); } + this.classpathPropFilePathStr = classpathPropFilePathString; return (S) this; } - /** - * Sets the classpath path via a StrProp (a Property of String type) to a - * properties file for the StdPropFileOnClasspathLoader to load. - * - * If no path is specified via either a String or StrProp, the path - * '/andhow.properties' is used.
- * - * Paths should start with a forward slash and have packages delimited by - * forward slashes. If the file name contains a dot, the path must - * start with a forward slash. Thus, it is good practice to add a validation - * rule to the StrProp used here to ensure it - * mustStartWith("/"). - * - * @param classpathPropFilePathProperty - * @return - */ + @Override public S setClasspathPropFilePath(StrProp classpathPropFilePathProperty) { if (classpathPropFilePathStr != null && classpathPropFilePathProperty != null) { @@ -137,73 +130,31 @@ public S setClasspathPropFilePath(StrProp classpathPropFilePathProperty) { return (S) this; } - /** - * If set, the properties file loaded by StdPropFileOnClasspathLoader must - * be found and a RuntimeException will be thrown if it is not found. - * - * This is not set by default, allowing the properties file to be optional. - * - * @return - */ + @Override public S classpathPropertiesRequired() { _missingClasspathPropFileAProblem = true; return (S) this; } - /** - * If set, the properties file loaded by StdPropFileOnClasspathLoader is - * optional and will not throw an error if it is not found. - * - * This is set by default, so there is no need to explicitly call it. - * - * @return - */ + @Override public S classpathPropertiesNotRequired() { _missingClasspathPropFileAProblem = false; return (S) this; } - /** - * Sets the filesystem path via a StrProp (a Property of String type) to a - * properties file for the StdPropFileOnFilesystemLoader to load. - * - * If no property is set to specify a path, or a property is set by has no - * value, this loader won't be used. If the property is specified but the - * specified file is missing, an error will be thrown based on the - * filesystemPropFileRequired flag. - * - * Paths should generally be absolute and correctly formed for the host - * environment. - * - * @param filesystemPropFilePath - * @return - */ + @Override public S setFilesystemPropFilePath(StrProp filesystemPropFilePath) { this.filesystemPropFilePathProp = filesystemPropFilePath; return (S) this; } - /** - * If set, the properties file loaded by StdPropFileOnFilesystemLoader must - * be found and a RuntimeException will be thrown if it is not found. - * - * This is not set by default, allowing the properties file to be optional. - * - * @return - */ + @Override public S filesystemPropFileRequired() { _missingFilesystemPropFileAProblem = true; return (S) this; } - /** - * If set, the properties file loaded by StdPropFileOnFilesystemLoader is - * optional and will not throw an error if it is not found. - * - * This is set by default, so there is no need to explicitly call it. - * - * @return - */ + @Override public S filesystemPropFileNotRequired() { _missingFilesystemPropFileAProblem = false; return (S) this; @@ -212,6 +163,7 @@ public S filesystemPropFileNotRequired() { /** * Allows system properties to be overridden. * + * @deprecated * @param properties */ public S setSystemProperties(Properties properties) { @@ -220,16 +172,37 @@ public S setSystemProperties(Properties properties) { } /** - * Allows the System environment to be overridden. + * Sets the System environment vars that AndHow will use to load Property values + * from for the {@Code StdEnvVarLoader} loader. + * + * If this method is not called or is called with a null Map, the actual env vars + * from {@Code System.getenv()} will be used. Calling this method with an empty + * Map will effectively prevent AndHow from receiving configuration from env vars. + * + * This does not actually change actual environment variables or what is + * returned from {@Code System.getenv()}. It only replaces what AndHow will see for env vars. * - * @param envProperties + * Note: There is no reason to use this method: Use one of the {@Code addFixedValue()} + * methods instead. Those methods are more clear, don't have to parse values, and + * (unlike this method) are not deprecated. + * + * @deprecated This method will be removed in a future release - it has no meaningful + * usage. Use the addFixedValue() methods instead. + * @param newEnvProperties * @return */ - public S setEnvironmentProperties(Map envProperties) { - this.envProperties = envProperties; + public S setEnvironmentProperties(Map newEnvProperties) { + + if (newEnvProperties != null) { + this.envProperties = new HashMap<>(); + this.envProperties.putAll(newEnvProperties); + } else { + this.envProperties = null; + } return (S) this; } + @Override public S setStandardLoaders(List> newStandardLoaders) { standardLoaders.clear(); @@ -238,6 +211,7 @@ public S setStandardLoaders(List> newStandardLoa return (S) this; } + @Override public S setStandardLoaders(Class... newStandardLoaders) { standardLoaders.clear(); @@ -249,6 +223,7 @@ public S setStandardLoaders(Class... newStandardLoader return (S) this; } + @Override public S insertLoaderBefore( Class insertBeforeThisLoader, Loader loaderToInsert) { @@ -263,6 +238,7 @@ public S insertLoaderBefore( return (S) this; } + @Override public S insertLoaderAfter( Class insertAfterThisLoader, Loader loaderToInsert) { diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/api/AppFatalException.java b/andhow-core/src/main/java/org/yarnandtail/andhow/api/AppFatalException.java index 9e1eb255..5e64c1d9 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/api/AppFatalException.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/api/AppFatalException.java @@ -24,7 +24,7 @@ public class AppFatalException extends RuntimeException { */ public AppFatalException(String message, Throwable throwable) { super(message, throwable); - this.problems = new ProblemList(); + this.problems = ProblemList.EMPTY_PROBLEM_LIST; } /** @@ -34,7 +34,7 @@ public AppFatalException(String message, Throwable throwable) { */ public AppFatalException(String message) { super(message); - this.problems = new ProblemList(); + this.problems = ProblemList.EMPTY_PROBLEM_LIST; } public AppFatalException(String message, ProblemList problems) { @@ -58,6 +58,17 @@ public AppFatalException(String message, Problem problem) { } } + public AppFatalException(Problem problem) { + super(problem != null?problem.getFullMessage():"Unknown AndHow fatal exception"); + + if (problem != null) { + this.problems = new ProblemList(); + this.problems.add(problem); + } else { + this.problems = ProblemList.EMPTY_PROBLEM_LIST; + } + } + public ProblemList getProblems() { return problems; } diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/api/ValidatedValuesWithContext.java b/andhow-core/src/main/java/org/yarnandtail/andhow/api/ValidatedValuesWithContext.java index a51b6afc..855e80bc 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/api/ValidatedValuesWithContext.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/api/ValidatedValuesWithContext.java @@ -3,18 +3,18 @@ import java.util.List; /** - * Extention of ValueMap that adds contextual information to the ValueMap. + * Extention of ValidatedValues that adds contextual information. * - * ValueMap has all the needed info to provide values for Proerties during runtime. + * ValidatedValues has all the needed info to provide values for Properties during runtime. * This class provides more metadata, such as where a value was loaded from, if * there are Problems encountered during value loading and which values were loaded * by which Loader, etc.. * - * During startup, a mutable version of ValueMapWithContext if incrementally loaded + * During startup, a mutable version of ValueMapWithContext is incrementally loaded * with values and reported issues. After loading is complete, values are copied - * to immutable versions of ValueMap and ValueMapWithContext. ValueMap is used - * to fetch values as needed, ValueMapWithContext provides metadata on values if - * needed. + * to an immutable ValidatedValues and ValidatedValuesWithContext. + * ValidatedValues is used to fetch values as needed, ValidatedValuesWithContext + * provides metadata on values if needed. * * @author eeverman */ diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/internal/ConstructionProblem.java b/andhow-core/src/main/java/org/yarnandtail/andhow/internal/ConstructionProblem.java index f3ce61f4..b0256988 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/internal/ConstructionProblem.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/internal/ConstructionProblem.java @@ -319,4 +319,39 @@ public String getProblemDescription() { AndHowInit.class.getCanonicalName(), joined); } } + + public static class InitiationLoopException extends ConstructionProblem { + AndHow.Initialization originalInit; + AndHow.Initialization secondInit; + + public InitiationLoopException(AndHow.Initialization originalInit, AndHow.Initialization secondInit) { + this.originalInit = originalInit; + this.secondInit = secondInit; + } + + public AndHow.Initialization getOriginalInit() { + return originalInit; + } + + public AndHow.Initialization getSecondInit() { + return secondInit; + } + + + @Override + public String getProblemDescription() { + + return "AndHow detected a loop during initiation. " + + "Likely causes are: " + System.lineSeparator() + + "- Multiple places in application code where AndHow.instance(AndHowConfiguration) is called" + System.lineSeparator() + + "- static initiation blocks or static variables that are initiated refering to the value of an AndHow Property" + System.lineSeparator() + + "- AndHow Properties that refer to the value of other AndHow properties in their construction" + System.lineSeparator() + + "::The first line in the stack trace following this error referring to your application code is likely causing the initiation loop::"; + } + + @Override + public String getFullMessage() { + return getProblemDescription(); + } + } } diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/load/BaseLoader.java b/andhow-core/src/main/java/org/yarnandtail/andhow/load/BaseLoader.java index 4cf3f92f..e27ed15f 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/load/BaseLoader.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/load/BaseLoader.java @@ -31,7 +31,7 @@ public List getInstanceConfig() { public SamplePrinter getConfigSamplePrinter() { return null; //Each implementation needs to provide its own. } - + /** * Util method to load a String to a property by name. * @@ -45,47 +45,62 @@ public SamplePrinter getConfigSamplePrinter() { */ protected void attemptToAdd(StaticPropertyConfigurationInternal appConfigDef, List values, ProblemList loaderProblems, String key, String strValue) { - - key = TextUtil.trimToNull(key); - - if (key != null) { - - String effKey = appConfigDef.getNamingStrategy().toEffectiveName(key); - - Property prop = appConfigDef.getProperty(effKey); - if (prop != null) { - - ValidatedValue pv = null; - - try { - pv = createValue(appConfigDef, prop, strValue); - } catch (ParsingException e) { - loaderProblems.add(new LoaderProblem.StringConversionLoaderProblem( - this, appConfigDef.getGroupForProperty(prop).getProxiedGroup(), prop, e.getProblemText())); - } - - if (pv != null) { - ValidatedValue dup = findDuplicateProperty(pv, values); - - if (dup == null) { - values.add(pv); - } else { - loaderProblems.add(new DuplicatePropertyLoaderProblem( - this, appConfigDef.getGroupForProperty(prop).getProxiedGroup(), prop)); - } - } - - } else if (this instanceof ReadLoader) { - ReadLoader rl = (ReadLoader)this; - if (rl.isUnknownPropertyAProblem()) { - loaderProblems.add(new UnknownPropertyLoaderProblem(this, key)); - } + Property prop = mapNametoProperty(appConfigDef, key); + + if (prop != null) { + + ValidatedValue validatedValue = null; + + try { + validatedValue = createValue(appConfigDef, prop, strValue); + } catch (ParsingException e) { + loaderProblems.add(new LoaderProblem.StringConversionLoaderProblem( + this, appConfigDef.getGroupForProperty(prop).getProxiedGroup(), prop, e.getProblemText())); } + attemptToAddIfNotDuplicate(appConfigDef, values, loaderProblems, validatedValue); + + } else if (this instanceof ReadLoader) { + ReadLoader rl = (ReadLoader)this; + if (rl.isUnknownPropertyAProblem()) { + loaderProblems.add(new UnknownPropertyLoaderProblem(this, key)); + } } + + } + + /** + * Util method to load an Object value to a named Property. + * + * Intended for code-based loaders where Property names are specified with Object based keys. + * This would happen with hardcoded / in-code loaders, likely during testing, where property + * values can be specified as real objects, but the Properties themselves may not all be + * visible, so names are used instead of Property references. + * + * @param appConfigDef Used to look up the property name for find the actual property + * @param values List of PropertyValues to add to, which should be only the value of this loader. + * @param loaderProblems A list of Problems to add to if there is a loader related problem + * @param key The property name + * @param value The property value as an Object, already of the expected type for the Property. + */ + protected void attemptToAdd(StaticPropertyConfigurationInternal appConfigDef, List values, + ProblemList loaderProblems, String key, Object value) { + + Property prop = mapNametoProperty(appConfigDef, key); + + if (prop != null) { + + attemptToAdd(appConfigDef, values, loaderProblems, prop, value); + + } else if (this instanceof ReadLoader) { + ReadLoader rl = (ReadLoader)this; + if (rl.isUnknownPropertyAProblem()) { + loaderProblems.add(new UnknownPropertyLoaderProblem(this, key)); + } + } + } - /** * Util method to attempt to load an object of an unknown type to a property. @@ -98,23 +113,24 @@ protected void attemptToAdd(StaticPropertyConfigurationInternal appConfigDef, Li * @param values List of PropertyValues to add to, which should be only the value of this loader. * @param loaderProblems A list of LoaderProblems to add to if there is a loader related problem * @param prop The Property to load to - * @param value The Object to be loaded to this property + * @param value The Object to be loaded to this property. If a String and that does + * not match the Property type, parsing is attempted to convert it. */ protected void attemptToAdd(StaticPropertyConfigurationInternal appConfigDef, List values, ProblemList loaderProblems, Property prop, Object value) { if (prop != null) { - ValidatedValue pv = null; + ValidatedValue validatedValue = null; if (value.getClass().equals(prop.getValueType().getDestinationType())) { - pv = new ValidatedValue(prop, value); + validatedValue = new ValidatedValue(prop, value); } else if (value instanceof String) { try { - pv = createValue(appConfigDef, prop, value.toString()); + validatedValue = createValue(appConfigDef, prop, value.toString()); } catch (ParsingException e) { loaderProblems.add(new LoaderProblem.StringConversionLoaderProblem( this, appConfigDef.getGroupForProperty(prop).getProxiedGroup(), prop, e.getProblemText())); @@ -124,21 +140,38 @@ protected void attemptToAdd(StaticPropertyConfigurationInternal appConfigDef, Li loaderProblems.add( new ObjectConversionValueProblem(this, appConfigDef.getGroupForProperty(prop).getProxiedGroup(), prop, value)); } - - if (pv != null) { - - ValidatedValue dup = findDuplicateProperty(pv, values); - - if (dup == null) { - values.add(pv); - } else { - loaderProblems.add(new DuplicatePropertyLoaderProblem( - this, appConfigDef.getGroupForProperty(prop).getProxiedGroup(), prop)); - } - } + + attemptToAddIfNotDuplicate(appConfigDef, values, loaderProblems, validatedValue); } } + + /** + * Adds the ValidatedValue to the VV list if it is not a duplicate. + * This is the actual 'load' moment of the Loader: Adding it to this list means + * that the value has been loaded. + * + * Loader subclasses should use the other attemptToAdd methods - this one is invoked by those. + * + * @param appConfigDef Used to look up the property name for find the actual property + * @param values List of PropertyValues to add to, which should be only the value of this loader. + * @param loaderProblems A list of Problems to add to if there is a loader related problem + * @param validatedValue The validated value to load, which may be null which is a no-op. + */ + protected void attemptToAddIfNotDuplicate(StaticPropertyConfigurationInternal appConfigDef, List values, + ProblemList loaderProblems, ValidatedValue validatedValue) { + + if (validatedValue != null) { + ValidatedValue dup = findDuplicateProperty(validatedValue, values); + + if (dup == null) { + values.add(validatedValue); + } else { + loaderProblems.add(new DuplicatePropertyLoaderProblem( + this, appConfigDef.getGroupForProperty(validatedValue.getProperty()).getProxiedGroup(), validatedValue.getProperty())); + } + } + } protected ValidatedValue findDuplicateProperty(ValidatedValue current, List values) { for (ValidatedValue ref : values) { @@ -173,6 +206,26 @@ protected ValidatedValue createValue(StaticPropertyConfigurationInternal app return new ValidatedValue(prop, value); } + + /** + * Maps the passed Property name or alias to a Property or null if it cannot be found. + * @param appConfigDef The AppConfig + * @param name The name, which will be trimmed and converted to an effective name. + * @return The Property or null if it cannot be found. + */ + protected Property mapNametoProperty(StaticPropertyConfigurationInternal appConfigDef, String name) { + + name = TextUtil.trimToNull(name); + + if (name != null) { + + Property prop = appConfigDef.getProperty(name); + return prop; + + } else { + return null; + } + } @Override public void releaseResources() { diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/load/FixedValueLoader.java b/andhow-core/src/main/java/org/yarnandtail/andhow/load/FixedValueLoader.java index 6f940089..d9162bc5 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/load/FixedValueLoader.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/load/FixedValueLoader.java @@ -7,12 +7,14 @@ /** * A utility loader that is used internally to put fixed values into the effective - * list of values. + * list of configured values. * * This loader does not trim incoming values for String type properties - they are * assumed to already be in final form. * This loader considers it a problem to be passed unrecognized properties - * and will throw a RuntimeException if that happens. + * and will throw a RuntimeException if that happens, though this can be set + * false to allow fixed values set via name to be ignored if the name is not + * recognized. * * @author eeverman */ @@ -20,39 +22,75 @@ public class FixedValueLoader extends BaseLoader implements ReadLoader { protected boolean unknownPropertyAProblem = true; - protected List values = new ArrayList(); + protected final List values = new ArrayList(); + + protected final List keyObjectPairValues = new ArrayList(); public FixedValueLoader() { } - + + /** + * Set property values as PropertyValues, which require live + * references to each Property. + * Values set in this way are additive to properties set via + * setKopValues and duplicate properties between the + * two will nominally be considered duplicates. + * + * @param values + */ public void setPropertyValues(List values) { if (values != null) { this.values.addAll(values); } } - + + /** + * @deprecated This method duplicates functionality and will + * be removed. The logic of this method is slightly wrong as + * well (compared to the List version): As this method is currently + * implemente, an empty array will NOT erase all values. The + * Other List based methods would erase all values in that case. + * @param values + */ public void setPropertyValues(PropertyValue... values) { if (values != null && values.length > 0) { this.values.addAll(Arrays.asList(values)); } } - + + /** + * Set property values as KeyObjectPair's. + * Values set in this way are additive to properties set via + * setPropertyValues and duplicate properties between the + * two will nominally be considered duplicates. + * + * @param values + */ + public void setKeyObjectPairValues(List values) { + if (values != null) { + keyObjectPairValues.addAll(values); + } + } + @Override public LoaderValues load(StaticPropertyConfigurationInternal appConfigDef, ValidatedValuesWithContext existingValues) { - - if (values != null && values.size() > 0) { - List vvs = new ArrayList(values.size()); - - for (int i = 0; i < values.size(); i++) { - ValidatedValue vv = new ValidatedValue(values.get(i).getProperty(), values.get(i).getValue()); - vvs.add(vv); - } - - return new LoaderValues(this, vvs, ProblemList.EMPTY_PROBLEM_LIST); - - } else { - return new LoaderValues(this, Collections.emptyList(), ProblemList.EMPTY_PROBLEM_LIST); - } + + List vvs = new ArrayList(values.size()); + ProblemList problems = new ProblemList(); + + //Add all the PropertyValue's. The Property and value references are 'live', + //so lots of potential errors are not possible, however, the value type may + //not match the Property, so use 'attemptToAdd' to verify. + values.stream().forEach( + v -> this.attemptToAdd(appConfigDef, vvs, problems, v.getProperty(), v.getValue()) + ); + + //Add all the KeyObjectPairs + keyObjectPairValues.stream().forEach( + kop -> this.attemptToAdd(appConfigDef, vvs, problems, kop.getName(), kop.getValue()) + ); + + return new LoaderValues(this, vvs, problems); } @@ -63,7 +101,7 @@ public boolean isTrimmingRequiredForStringValues() { @Override public String getSpecificLoadDescription() { - return "a list of fixed values passed in by the construction code (not dynamically loaded)"; + return "a list of fixed values passed in during startup (not dynamically loaded)"; } @Override @@ -88,7 +126,8 @@ public boolean isUnknownPropertyAProblem() { @Override public void releaseResources() { - values = null; + values.clear(); + keyObjectPairValues.clear(); } } diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/load/KeyObjectPair.java b/andhow-core/src/main/java/org/yarnandtail/andhow/load/KeyObjectPair.java new file mode 100644 index 00000000..815c9a76 --- /dev/null +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/load/KeyObjectPair.java @@ -0,0 +1,75 @@ +package org.yarnandtail.andhow.load; + +import org.yarnandtail.andhow.api.ParsingException; +import org.yarnandtail.andhow.util.TextUtil; + +/** + * Key-Object Pair + * Contains a String name and an Object value. + * Names are always trimmed (leading and trailing spaces removed) because they are + * expected to match up to AndHow Property canonical names or alias, which don't includes spaces. + * After trimming, names must be non-empty and non-null. + */ +public class KeyObjectPair { + + private String name; + private Object value; + + /** + * Construct an instance with a null value. + * @param name A name that will be trimmed to remove all leading and trailing spaces. + * @throws ParsingException If the name is empty after trimming. + */ + public KeyObjectPair(final String name) throws ParsingException { + this(name, null); + } + + /** + * New instance with name and value. + * @param name A name that will be trimmed to remove all leading and trailing spaces. + * @param value The value, which will not be trimmed or modified. + * @throws ParsingException If the name is empty after trimming. + */ + public KeyObjectPair(final String name, final Object value) throws ParsingException { + String cleanName = TextUtil.trimToNull(name); + + if (cleanName == null) { + throw new ParsingException("The key (parameter name) cannot be empty or null", name); + } + + this.name = cleanName; + this.value = value; + } + + /** + * The KeyObjectPair name, which has been trimmed to remove leading and trailing spaces. + * @return + */ + public String getName() { + return name; + } + + /** + * The value exactly as passed in the constructor, which may be null. + * @return The value as set. + */ + public Object getValue() { + return value; + } + + //Hashcode and Equals + //Normally a class like this would override these methods, but AndHow never needs to compare + //equality of this class, or if it does, is only concerned with the name and equality based + //only on name seems 'broken'. + + /** + * String representation: name : "value" + * + * If the value is null, the string [null] is used. + * @return A formatted string version of the instance. + */ + @Override + public String toString() { + return name + " : " + "\"" + (value!=null?value.toString():"[null]") + "\""; + } +} diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/load/PropFileOnClasspathLoader.java b/andhow-core/src/main/java/org/yarnandtail/andhow/load/PropFileOnClasspathLoader.java index 83d632e0..e6a8c1c3 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/load/PropFileOnClasspathLoader.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/load/PropFileOnClasspathLoader.java @@ -7,8 +7,8 @@ import org.yarnandtail.andhow.internal.LoaderProblem; /** - * Reads from a Java .property file on the classpath, following standard java - * conventions for the structure of those file. + * Reads from a Java .properties file on the classpath, following standard java + * conventions for the structure of those key:value pair files. * * This loader finds the properties file via either a String or StrProperty * specified in the constructor. If the StrProperty is used, an earlier an diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/load/std/StdFixedValueLoader.java b/andhow-core/src/main/java/org/yarnandtail/andhow/load/std/StdFixedValueLoader.java index 6c94ceef..85111360 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/load/std/StdFixedValueLoader.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/load/std/StdFixedValueLoader.java @@ -1,6 +1,6 @@ package org.yarnandtail.andhow.load.std; -import org.yarnandtail.andhow.api.StandardLoader; +import org.yarnandtail.andhow.api.*; import org.yarnandtail.andhow.load.FixedValueLoader; /** @@ -27,7 +27,7 @@ *

Basic Behaviors

*
    *
  • Pre-trims String values: No (Individual Properties may still trim values) - *
  • Complains about unrecognized properties: NA - This is not possible + *
  • Complains about unrecognized properties: Yes - Only applies to Properties specified by name *
  • Default behavior: None - This loader is only active if values are directly set as shown below *
*

Loader Details and Configuration

@@ -43,7 +43,10 @@ * * {@literal @}Override public AndHowConfiguration getConfiguration() { * return StdConfig.instance() - * .addFixedValue(MY_PROP, "some value"); //MY_PROP is some visible property + * .addFixedValue(MY_PROP, 23L); //MY_PROP is some visible property of type Long. + * .addFixedValue("A_PROPERTY_NAME", "abc") //A name or alias of a Property works as well + * //In both cases, the value (23L or "abc") must be of the same type as the Property or + * //an error will be thrown. * } * } * diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/load/std/StdPropFileOnClasspathLoader.java b/andhow-core/src/main/java/org/yarnandtail/andhow/load/std/StdPropFileOnClasspathLoader.java index ff3b7166..c59cfa6c 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/load/std/StdPropFileOnClasspathLoader.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/load/std/StdPropFileOnClasspathLoader.java @@ -42,13 +42,39 @@ * Full details on how Java parses properties files can be found in the * properties file specification. *

- * Configuring the name or classpath of the properties file can be used to - * enable different configuration profiles based on the environment. - * For instance, a system property could specify that {@code /test.properties} - * be used on a test server and {@code /production.properties} on a production - * server. An example of configuring the property file path: - *
- *

+ * Since resources on the test classpath override those on the main classpath,
+ * its easy to have separate configurations for production and testing simply
+ * by having two {@code andhow.properties} files, one on the main classpath
+ * and one on test.  There are many other options with classpath properties files.
+ * 

+ * To change the name of the properties file used for testing, a class like the + * example below can be added to your test classpath: + *

{@Code
+ * import org.yarnandtail.andhow.*;
+ * import org.yarnandtail.andhow.property.StrProp;
+ *
+ * public class UsePropertyFileOnClasspath implements AndHowTestInit {
+ *
+ *   {@literal @}Override
+ *   public AndHowConfiguration getConfiguration() {
+ *     return  StdConfig.instance()
+ *       .setClasspathPropFilePath("/my_test_configuration_properties_file.prop");
+ *   }
+ * }
+ * }
+ * If AndHow finds a {@Code AndHowTestInit} implementation on the classpath, + * it uses it in preference to other initiation points. + * With this only on the test classpath, AndHow will still use the default + * {@code andhow.properties} for production. + *

+ * With slightly fancier configuration, something like {@code /staging.properties} + * could be used in a staging environment and {@code /production.properties} + * used in production. In a case like this we need the classpath to be + * configurable, not hard-coded like the example above. To do that we need + * a Property to configure it with, then we tell AndHow which property was + * used.
+ * Here is an example of a configurable classpath: + *

{@Code
  * import org.yarnandtail.andhow.*;
  * import org.yarnandtail.andhow.property.StrProp;
  * 
@@ -63,7 +89,7 @@
  *       .setClasspathPropFilePath(MY_CLASSPATH);
  *   }
  * }
- * 
+ * }
* The code above adds the property {@code MY_CLASSPATH} * (the name is arbitrary) which is used to configure the * {@code StdPropFileOnClasspathLoader} with a custom property file location. @@ -71,6 +97,9 @@ * see if a value has been loaded for {@code MY_CLASSPATH} by any prior loader. * If a value is present, the loader tries to load from the configured classpath. * If no value is configured, the default classpath is assumed. + * Properties file loaders are the last loaders, so configuration the classpath + * can be done by virtually anything: environment vars, system properties, + * command line args, JNDI... * *

This is a Standard Loader

* Like all {@code StandardLoader}'s, this loader is intended to be auto-created diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/property/BigDecProp.java b/andhow-core/src/main/java/org/yarnandtail/andhow/property/BigDecProp.java new file mode 100644 index 00000000..e80a6f22 --- /dev/null +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/property/BigDecProp.java @@ -0,0 +1,107 @@ +package org.yarnandtail.andhow.property; + +import org.yarnandtail.andhow.api.*; +import org.yarnandtail.andhow.valid.BigDecValidator; +import org.yarnandtail.andhow.valuetype.BigDecType; + +import java.math.BigDecimal; +import java.util.List; + +/** + * A Property that refers to a BigDecimal value. + * + * By default this uses the TrimToNullTrimmer, which removes all whitespace from + * the value and ultimately null if the value is all whitespace. The String + * constructor version is used when creating instances of BigDecimal. + * + * @author chace86 + */ +public class BigDecProp extends PropertyBase { + + /** + * Construct an instance of BigDecProp + * @param defaultValue default value + * @param required make the property required or not + * @param shortDesc short description of the property + * @param validators list of validators for the property + * @param aliases aliases of the property + * @param paramType property type + * @param valueType property value type + * @param trimmer trimmer associated with the property + * @param helpText help text of the property + */ + public BigDecProp(BigDecimal defaultValue, boolean required, String shortDesc, List> validators, + List aliases, PropertyType paramType, ValueType valueType, Trimmer trimmer, + String helpText) { + + super(defaultValue, required, shortDesc, validators, aliases, paramType, valueType, trimmer, helpText); + } + + /** + * Return an instance of BigDecBuilder + */ + public static BigDecBuilder builder() { + return new BigDecBuilder(); + } + + /** + * Build a BigDecProp + */ + public static class BigDecBuilder extends PropertyBuilderBase { + + /** + * Construct an instance of BigDecBuilder + */ + public BigDecBuilder() { + instance = this; + valueType(BigDecType.instance()); + trimmer(TrimToNullTrimmer.instance()); + } + + @Override + public BigDecProp build() { + return new BigDecProp(_defaultValue, _nonNull, _desc, _validators, + _aliases, PropertyType.SINGLE_NAME_VALUE, _valueType, _trimmer, _helpText); + } + + /** + * The property must be greater than the reference + * @param reference value the property must be greater than + * @return the builder instance + */ + public BigDecBuilder mustBeGreaterThan(BigDecimal reference) { + validation(new BigDecValidator.GreaterThan(reference)); + return instance; + } + + /** + * The property must be greater than or equal to the reference + * @param reference value the property must be greater than or equal to + * @return the builder instance + */ + public BigDecBuilder mustBeGreaterThanOrEqualTo(BigDecimal reference) { + validation(new BigDecValidator.GreaterThanOrEqualTo(reference)); + return instance; + } + + /** + * The property must be less than the reference + * @param reference value the property must be less than + * @return the builder instance + */ + public BigDecBuilder mustBeLessThan(BigDecimal reference) { + validation(new BigDecValidator.LessThan(reference)); + return instance; + } + + /** + * The property must be less than or equal to the reference + * @param reference value the property must be less than or equal to + * @return the builder instance + */ + public BigDecBuilder mustBeLessThanOrEqualTo(BigDecimal reference) { + validation(new BigDecValidator.LessThanOrEqualTo(reference)); + return instance; + } + } +} diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/property/DblProp.java b/andhow-core/src/main/java/org/yarnandtail/andhow/property/DblProp.java index f42a210d..f786cbad 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/property/DblProp.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/property/DblProp.java @@ -13,6 +13,21 @@ * * By default this uses the TrimToNullTrimmer, which removes all whitespace from * the value and ultimately null if the value is all whitespace. + * + * If a DblProp is configured as a string, such as from a properties file, on + * command line, environment variable, etc., the value MAY include a trailing + * 'D' of 'F' (lower case is ok too), as is done with Java literals. This is + * different than the behavior of LngProp, but is a result of how Java parses + * these values. + * + * E.g., here are several correct ways to spec double value in a properties file: + * + * name.of.my.double.property.MY_PROPERTY_1 = 90.00 + * name.of.my.double.property.MY_PROPERTY_2 = 80.00D + * name.of.my.double.property.MY_PROPERTY_3 = 70.00F + * name.of.my.double.property.MY_PROPERTY_4 = 60.00d + * name.of.my.double.property.MY_PROPERTY_5 = 4 + * * * @author eeverman */ diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/property/LngProp.java b/andhow-core/src/main/java/org/yarnandtail/andhow/property/LngProp.java index 089d4386..150e87d9 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/property/LngProp.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/property/LngProp.java @@ -13,6 +13,14 @@ * * By default this uses the TrimToNullTrimmer, which removes all whitespace from * the value and ultimately null if the value is all whitespace. + * + * If a LngProp is configured as a string, such as from a properties file, on + * command line, environment variable, etc., the value does NOT include a trailing + * 'L', as is done with Java literals. E.g., this is the correct way to spec + * a long value in a properties file: + * + * name.of.my.long.property.MY_PROPERTY = 90 + * * * @author eeverman */ diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/property/StrProp.java b/andhow-core/src/main/java/org/yarnandtail/andhow/property/StrProp.java index 4b9307e9..977de4c9 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/property/StrProp.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/property/StrProp.java @@ -9,7 +9,7 @@ * A Property that refers to a String value. * * All the basic Java types use a three letter abv. to keep declaration lines - * short, in the form of: [Type]Prop + * short, in the form of: [Type]Prop * * By default, this uses the QuotedSpacePreservingTrimmer, which will keep * whitespace inside double quotes. @@ -17,23 +17,19 @@ * @author eeverman */ public class StrProp extends PropertyBase { - - public StrProp( - String defaultValue, boolean nonNull, String shortDesc, List> validators, - List aliases, PropertyType paramType, ValueType valueType, Trimmer trimmer, - String helpText) { - + + public StrProp(String defaultValue, boolean nonNull, String shortDesc, List> validators, + List aliases, PropertyType paramType, ValueType valueType, Trimmer trimmer, String helpText) { + super(defaultValue, nonNull, shortDesc, validators, aliases, paramType, valueType, trimmer, helpText); } - + public static StrBuilder builder() { return new StrBuilder(); } - - + public static class StrBuilder extends PropertyBuilderBase { - public StrBuilder() { instance = this; valueType(StrType.instance()); @@ -43,35 +39,40 @@ public StrBuilder() { @Override public StrProp build() { - return new StrProp(_defaultValue, _nonNull, _desc, _validators, - _aliases, PropertyType.SINGLE_NAME_VALUE, _valueType, _trimmer, _helpText); + return new StrProp(_defaultValue, _nonNull, _desc, _validators, _aliases, PropertyType.SINGLE_NAME_VALUE, + _valueType, _trimmer, _helpText); } - + public StrBuilder mustMatchRegex(String regex) { this.validation(new StringValidator.Regex(regex)); return this; } - + public StrBuilder mustStartWith(String prefix) { this.validation(new StringValidator.StartsWith(prefix, false)); return this; } - + public StrBuilder mustStartWithIgnoreCase(String prefix) { this.validation(new StringValidator.StartsWith(prefix, true)); return this; } - + public StrBuilder mustEndWith(String sufix) { this.validation(new StringValidator.EndsWith(sufix, false)); return this; } - + public StrBuilder mustEndWithIgnoreCase(String sufix) { this.validation(new StringValidator.EndsWith(sufix, true)); return this; } + public StrBuilder mustEqual(String... values) { + this.validation(new StringValidator.Equals(values)); + return this; + } + } - + } diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/util/IOUtil.java b/andhow-core/src/main/java/org/yarnandtail/andhow/util/IOUtil.java index 04f31bfb..d4fe3b9f 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/util/IOUtil.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/util/IOUtil.java @@ -4,37 +4,59 @@ import java.nio.charset.Charset; /** - * + * Utility class to handle some IO purposes. * @author ericeverman */ public class IOUtil { + private static final String JAVA_TMP_KEY = "java.io.tmpdir"; - private static final String USER_DIR_KEY = "user.home"; - + + /** + * All utility methods are static. + */ private IOUtil() { //no instances } - + + /** + * Retrieve a String from an UTF-8 encoded classpath resource. + * @param path location on the classpath. + * @return a string reprensenting the entire content of the resource. + * @throws IOException when the resource cannot be loaded. + */ public static String getUTF8ResourceAsString(String path) throws IOException { InputStream in = IOUtil.class.getResourceAsStream(path); - + if (in == null) { throw new IOException("Unable to find the resource '" + path + "'"); } - + return toString(in, Charset.forName("UTF-8")); } - + + /** + * Retrieve a String from a classpath resource. + * @param path location on the classpath. + * @return a string reprensenting the entire content of the resource. + * @throws IOException when the resource cannot be loaded. + */ public static String getResourceAsString(String path, Charset encoding) throws IOException { InputStream in = IOUtil.class.getResourceAsStream(path); - + if (in == null) { throw new IOException("Unable to find the resource '" + path + "'"); } - + return toString(in, encoding); } - + + /** + * Build a String from the content on an InputStream using the encoding provided. + * @param input Stream containing the data to extract. + * @param encoding Charset used to read the input. + * @return String representing the content of the input. + * @throws IOException when the resource cannot be read. + */ public static String toString(InputStream input, Charset encoding) throws IOException { StringBuilder builder = new StringBuilder(); @@ -48,43 +70,54 @@ public static String toString(InputStream input, Charset encoding) throws IOExce if (builder.length() > 0) builder.setLength(builder.length() - System.lineSeparator().length()); - + return builder.toString(); } - + + /** + * Expands directory path, replacing known values like java.io.tmpdir + * w/ their values. Paths are assumed to use forward slashes, which are replaced + * on Windows systems to be backslashes. + * + * As the provided path has to be a directory, the returned value will end with + * a file separator character. + * + * @param path directory path to expand if needed. + * @return the expanded directory path. + */ public static String expandDirectoryPath(String path) { path = expandFilePath(path); - + if (! path.endsWith(String.valueOf(File.separatorChar))) { return path + File.separatorChar; } else { return path; } } - + /** * Expands a file or directory path, replacing known values like java.io.tmpdir * w/ their values. Paths are assumed to use forward slashes, which are replaced * on Windows systems to be backslashes. - * + * * The returned path may be either a file path (not ending with a separator) * or directory (ending with a separator). - * - * @param path - * @return + * + * @param path path to expand if needed. + * @return the expanded path. */ public static String expandFilePath(String path) { - + String tmpDir = System.getProperty(JAVA_TMP_KEY); path = path.trim(); - + //rm the trailing separatorChar from the temp directory if there is //possibly more to the path than just the temp directory. Otherwise the //sep char gets doubled up. if (path.length() > JAVA_TMP_KEY.length() && tmpDir.endsWith(System.getProperty("file.separator"))) { tmpDir = tmpDir.substring(0, tmpDir.length() - 1); } - + path = path.replace((CharSequence)JAVA_TMP_KEY, (CharSequence)tmpDir); path = path.replace('/', System.getProperty("file.separator").charAt(0)); return path; diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/util/TextUtil.java b/andhow-core/src/main/java/org/yarnandtail/andhow/util/TextUtil.java index 93ab1d24..03e46850 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/util/TextUtil.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/util/TextUtil.java @@ -324,7 +324,9 @@ public static List wrap(String in, int length) { /** * Find the first occurrence of one of an array of possible strings in another string, - * starting at the specified position. + * starting at the specified position, looking to the right. If there are more than one + * string arguments to be searched from, it returns the index of the argument which is + * of the lower index * * @param toBeSearched * @param searchFrom Same symantics as String.indexOf(String, int) @@ -334,7 +336,7 @@ public static List wrap(String in, int length) { public static int findFirstInstanceOf(String toBeSearched, int searchFrom, String... toBeFound) { int result = -1; - if (toBeSearched != null || toBeFound.length > 0) { + if (toBeSearched != null && toBeFound.length > 0) { for (String s : toBeFound) { int i = toBeSearched.indexOf(s, searchFrom); if (i > -1 && (i < result || result == -1)) { @@ -347,7 +349,11 @@ public static int findFirstInstanceOf(String toBeSearched, int searchFrom, Strin } /** - * + * Find the first occurrence of one of an array of possible strings in another string, + * starting at the specified position and seeking to the left. If there are more than + * one string arguments to be searched from, it returns the index of the argument which is + * of the higher index + * * @param toBeSearched * @param searchFrom Start looking from this position and to the left * @param toBeFound @@ -356,7 +362,7 @@ public static int findFirstInstanceOf(String toBeSearched, int searchFrom, Strin public static int findLastInstanceOf(String toBeSearched, int searchFrom, String... toBeFound) { int result = -1; - if (toBeSearched != null || toBeFound.length > 0) { + if (toBeSearched != null && toBeFound.length > 0) { for (String s : toBeFound) { int i = toBeSearched.lastIndexOf(s, searchFrom); if (i > result) { diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valid/BigDecValidator.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valid/BigDecValidator.java new file mode 100644 index 00000000..3ca8a6d8 --- /dev/null +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valid/BigDecValidator.java @@ -0,0 +1,125 @@ +package org.yarnandtail.andhow.valid; + +import org.yarnandtail.andhow.api.Validator; + +import java.math.BigDecimal; + +/** + * Abstract class implementing Validator interface for BigDec. + * Extended by nested static classes. The nested classes implement + * constraints that may be used when building the property. + */ +public abstract class BigDecValidator implements Validator { + + final BigDecimal ref; + + /** + * Base constructor of BigDecValidator constraints + * @param ref to be compared to property value + */ + BigDecValidator(BigDecimal ref) { + this.ref = ref; + } + + @Override + public boolean isSpecificationValid() { + return ref != null; + } + + @Override + public String getInvalidSpecificationMessage() { + return "The constraint may not be null"; + } + + /** + * Validate that a BigDecimal is greater than a specified reference. + */ + public static class GreaterThan extends BigDecValidator { + /** + * Construct a GreaterThan property constraint + * @param ref to be compared to property value + */ + public GreaterThan(BigDecimal ref) { + super(ref); + } + + @Override + public boolean isValid(BigDecimal value) { + return (value != null) && (value.compareTo(ref) > 0); + } + + @Override + public String getTheValueMustDescription() { + return "be greater than " + ref; + } + } + + /** + * Validate that a BigDecimal is greater than or equal to a specified reference. + */ + public static class GreaterThanOrEqualTo extends BigDecValidator { + /** + * Construct a GreaterThanOrEqualTo property constraint + * @param ref to be compared to property value + */ + public GreaterThanOrEqualTo(BigDecimal ref) { + super(ref); + } + + @Override + public boolean isValid(BigDecimal value) { + return (value != null) && (value.compareTo(ref) >= 0); + } + + @Override + public String getTheValueMustDescription() { + return "be greater than or equal to " + ref; + } + } + + /** + * Validate that a BigDecimal is less than a specified reference. + */ + public static class LessThan extends BigDecValidator { + /** + * Construct a LessThan property constraint + * @param ref to be compared to property value + */ + public LessThan(BigDecimal ref) { + super(ref); + } + + @Override + public boolean isValid(BigDecimal value) { + return (value != null) && (value.compareTo(ref) < 0); + } + + @Override + public String getTheValueMustDescription() { + return "be less than " + ref; + } + } + + /** + * Validate that a BigDecimal is less than or equal to a specified reference. + */ + public static class LessThanOrEqualTo extends BigDecValidator { + /** + * Construct a LessThanOrEqualTo property constraint + * @param ref to be compared to property value + */ + public LessThanOrEqualTo(BigDecimal ref) { + super(ref); + } + + @Override + public boolean isValid(BigDecimal value) { + return (value != null) && (value.compareTo(ref) <= 0); + } + + @Override + public String getTheValueMustDescription() { + return "be less than or equal to " + ref; + } + } +} diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valid/DblValidator.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valid/DblValidator.java index 4b037c2d..ad3365df 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/valid/DblValidator.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valid/DblValidator.java @@ -24,7 +24,7 @@ public String getInvalidSpecificationMessage() { */ public static class GreaterThan extends DblValidator { - double ref; + private double ref; public GreaterThan(double ref) { this.ref = ref; @@ -50,7 +50,7 @@ public String getTheValueMustDescription() { */ public static class GreaterThanOrEqualTo extends DblValidator { - double ref; + private double ref; public GreaterThanOrEqualTo(double ref) { this.ref = ref; @@ -76,7 +76,7 @@ public String getTheValueMustDescription() { */ public static class LessThan extends DblValidator { - double ref; + private double ref; public LessThan(double ref) { this.ref = ref; @@ -102,7 +102,7 @@ public String getTheValueMustDescription() { */ public static class LessThanOrEqualTo extends DblValidator { - double ref; + private double ref; public LessThanOrEqualTo(double ref) { this.ref = ref; diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valid/IntValidator.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valid/IntValidator.java index 564a5aec..936de764 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/valid/IntValidator.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valid/IntValidator.java @@ -24,7 +24,7 @@ public String getInvalidSpecificationMessage() { */ public static class GreaterThan extends IntValidator { - int ref; + private int ref; public GreaterThan(int ref) { this.ref = ref; @@ -50,7 +50,7 @@ public String getTheValueMustDescription() { */ public static class GreaterThanOrEqualTo extends IntValidator { - int ref; + private int ref; public GreaterThanOrEqualTo(int ref) { this.ref = ref; @@ -76,7 +76,7 @@ public String getTheValueMustDescription() { */ public static class LessThan extends IntValidator { - int ref; + private int ref; public LessThan(int ref) { this.ref = ref; @@ -102,7 +102,7 @@ public String getTheValueMustDescription() { */ public static class LessThanOrEqualTo extends IntValidator { - int ref; + private int ref; public LessThanOrEqualTo(int ref) { this.ref = ref; diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valid/LngValidator.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valid/LngValidator.java index 0f9cda60..3632372d 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/valid/LngValidator.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valid/LngValidator.java @@ -50,7 +50,7 @@ public String getTheValueMustDescription() { */ public static class GreaterThanOrEqualTo extends LngValidator { - long ref; + private long ref; public GreaterThanOrEqualTo(long ref) { this.ref = ref; @@ -76,7 +76,7 @@ public String getTheValueMustDescription() { */ public static class LessThan extends LngValidator { - long ref; + private long ref; public LessThan(long ref) { this.ref = ref; @@ -102,7 +102,7 @@ public String getTheValueMustDescription() { */ public static class LessThanOrEqualTo extends LngValidator { - long ref; + private long ref; public LessThanOrEqualTo(long ref) { this.ref = ref; diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valid/StringValidator.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valid/StringValidator.java index 79fa04fb..16504142 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/valid/StringValidator.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valid/StringValidator.java @@ -1,5 +1,7 @@ package org.yarnandtail.andhow.valid; +import java.util.Arrays; + import org.yarnandtail.andhow.api.Validator; /** @@ -8,14 +10,49 @@ * @author ericeverman */ public class StringValidator { - + + /** + * Validate that a string is one from the specified set. + */ + public static class Equals implements Validator { + + String[] values; + + public Equals(String... values) { + this.values = values; + } + + @Override + public boolean isSpecificationValid() { + return values != null && values.length != 0; + } + + @Override + public String getInvalidSpecificationMessage() { + return "The Equals list must contain at least one value"; + } + + @Override + public boolean isValid(String value) { + if (value != null) { + return Arrays.stream(values).anyMatch(value::equals); + } + return false; + } + + @Override + public String getTheValueMustDescription() { + return "be equal to one of '" + Arrays.deepToString(values) + "'"; + } + } + /** * Validate that a string starts with a specific string. */ public static class StartsWith implements Validator { - String prefix; - boolean ignoreCase; + private String prefix; + private boolean ignoreCase; public StartsWith(String prefix, boolean ignoreCase) { this.prefix = prefix; @@ -29,7 +66,7 @@ public boolean isSpecificationValid() { @Override public String getInvalidSpecificationMessage() { - return "The StartWith expression cannot be null"; + return "The StartsWith expression cannot be null"; } @Override @@ -43,21 +80,20 @@ public boolean isValid(String value) { } return false; } - + @Override public String getTheValueMustDescription() { return "start with '" + prefix + "'"; } } - /** * Validate that a string ends with a specific string. */ public static class EndsWith implements Validator { - String sufix; - boolean ignoreCase; + private String sufix; + private boolean ignoreCase; public EndsWith(String sufix, boolean ignoreCase) { this.sufix = sufix; @@ -85,19 +121,19 @@ public boolean isValid(String value) { } return false; } - + @Override public String getTheValueMustDescription() { return "end with '" + sufix + "'"; } } - + /** * Validate based on a regex string. */ public static class Regex implements Validator { - String regex; + private String regex; public Regex(String regex) { this.regex = regex; @@ -128,11 +164,11 @@ public boolean isValid(String value) { return false; } } - + @Override public String getTheValueMustDescription() { return "match the regex expression '" + regex + "'"; } - } + } } diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/BigDecType.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/BigDecType.java new file mode 100644 index 00000000..0bec4fe9 --- /dev/null +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/BigDecType.java @@ -0,0 +1,52 @@ +package org.yarnandtail.andhow.valuetype; + +import org.yarnandtail.andhow.api.ParsingException; + +import java.math.BigDecimal; + +/** + * Type representation of Java BigDecimal objects. + * + * This class is threadsafe and uses a singleton pattern to prevent multiple + * instances, since all users can safely use the same instance. + * + * @author chace86 + */ +public class BigDecType extends BaseValueType { + + private static final BigDecType INSTANCE = new BigDecType(); + + private BigDecType() { + super(BigDecimal.class); + } + + /** + * Construct an instance of BigDecType + */ + public static BigDecType instance() { + return INSTANCE; + } + + + /** + * {@inheritDoc} + * For more information on how the sourceValue is parsed, see + * BigDecimal String constructor. + */ + @Override + public BigDecimal parse(String sourceValue) throws ParsingException { + if (sourceValue == null) { + return null; + } + try { + return new BigDecimal(sourceValue); + } catch (Exception e) { + throw new ParsingException("Unable to convert to a BigDecimal numeric value", sourceValue, e); + } + } + + @Override + public BigDecimal cast(Object o) throws RuntimeException { + return (BigDecimal)o; + } +} diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/BolType.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/BolType.java index 0915e767..2206b785 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/BolType.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/BolType.java @@ -17,11 +17,20 @@ public class BolType extends BaseValueType { private BolType() { super(Boolean.class); } - + + /** + * @deprecated since 0.4.1. Use {@link #instance()} instead + * + * @return An instance of the {@link #BolType()} + */ + @Deprecated() public static BolType get() { - return instance; + return instance(); } - + + /** + * @return An instance of the {@link #BolType()} + */ public static BolType instance() { return instance; } diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/DblType.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/DblType.java index 94847605..fa1aac6b 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/DblType.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/DblType.java @@ -17,11 +17,20 @@ public class DblType extends BaseValueType { private DblType() { super(Double.class); } - + + /** + * @deprecated since 0.4.1. Use {@link #instance()} instead + * + * @return An instance of the {@link #DblType()} + */ + @Deprecated() public static DblType get() { - return instance; + return instance(); } - + + /** + * @return An instance of the {@link #DblType()} + */ public static DblType instance() { return instance; } diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/FlagType.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/FlagType.java index 0285b986..042fecd6 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/FlagType.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/FlagType.java @@ -17,11 +17,20 @@ public class FlagType extends BaseValueType { private FlagType() { super(Boolean.class); } - + + /** + * @deprecated since 0.4.1. Use {@link #instance()} instead + * + * @return An instance of the {@link #FlagType()} + */ + @Deprecated() public static FlagType get() { - return instance; + return instance(); } - + + /** + * @return An instance of the {@link #FlagType()} + */ public static FlagType instance() { return instance; } diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/IntType.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/IntType.java index d371b3c6..7ca664b5 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/IntType.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/IntType.java @@ -17,11 +17,20 @@ public class IntType extends BaseValueType { private IntType() { super(Integer.class); } - + + /** + * @deprecated since 0.4.1. Use {@link #instance()} instead + * + * @return An instance of the {@link #IntType()} + */ + @Deprecated() public static IntType get() { - return instance; + return instance(); } - + + /** + * @return An instance of the {@link #IntType()} + */ public static IntType instance() { return instance; } diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/LngType.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/LngType.java index 3bbfd154..b57cf81f 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/LngType.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/LngType.java @@ -17,11 +17,20 @@ public class LngType extends BaseValueType { private LngType() { super(Long.class); } - + + /** + * @deprecated since 0.4.1. Use {@link #instance()} instead + * + * @return An instance of the {@link #LngType()} + */ + @Deprecated() public static LngType get() { - return instance; + return instance(); } - + + /** + * @return An instance of the {@link #LngType()} + */ public static LngType instance() { return instance; } diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/LocalDateTimeType.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/LocalDateTimeType.java index 120fe847..b6778e7b 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/LocalDateTimeType.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/LocalDateTimeType.java @@ -23,11 +23,20 @@ public class LocalDateTimeType extends BaseValueType { private LocalDateTimeType() { super(LocalDateTime.class); } - + + /** + * @deprecated since 0.4.1. Use {@link #instance()} instead + * + * @return An instance of the {@link #LocalDateTimeType()} + */ + @Deprecated public static LocalDateTimeType get() { - return instance; + return instance(); } - + + /** + * @return An instance of the {@link #LocalDateTimeType()} + */ public static LocalDateTimeType instance() { return instance; } @@ -45,9 +54,9 @@ public static LocalDateTimeType instance() { *
  • 2011-12-03T00:15:30 - The first hour is hour zero *
  • 2011-12-03T23:00:00 - The last hour is hour 23 * - * @param sourceValue - * @return - * @throws ParsingException + * @param sourceValue The @{@link String} value to be parsed into a @{@link LocalDateTime} + * @return A valid @{@link LocalDateTime} + * @throws ParsingException If the @{@link String} can't be parsed into a @{@link LocalDateTime} */ @Override public LocalDateTime parse(String sourceValue) throws ParsingException { diff --git a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/StrType.java b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/StrType.java index 7ed37302..f32a4cb0 100644 --- a/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/StrType.java +++ b/andhow-core/src/main/java/org/yarnandtail/andhow/valuetype/StrType.java @@ -17,7 +17,10 @@ public class StrType extends BaseValueType { private StrType() { super(String.class); } - + + /** + * @return An instance of the {@link #StrType()} + */ public static StrType instance() { return instance; } diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowCoreTestBase.java b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowCoreTestBase.java index 79a40c2e..f99bab10 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowCoreTestBase.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowCoreTestBase.java @@ -2,7 +2,11 @@ import java.util.Properties; import javax.naming.NamingException; -import org.junit.*; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.springframework.mock.jndi.SimpleNamingContextBuilder; /** @@ -45,57 +49,50 @@ public class AndHowCoreTestBase { */ private static SimpleNamingContextBuilder builder; - @BeforeClass + @BeforeAll public static void killAndHowStateBeforeClass() { AndHowCoreTestUtil.destroyAndHow(); } - @BeforeClass + @BeforeAll public static void storeSysPropsBeforeClass() { beforeClassSystemProps = AndHowCoreTestUtil.clone(System.getProperties()); } - @Before + @BeforeEach public void killAndHowStateBeforeTest() { AndHowCoreTestUtil.destroyAndHow(); } - @Before + @BeforeEach public void storeSysPropsBeforeTest() { beforeTestSystemProps = AndHowCoreTestUtil.clone(System.getProperties()); } - @After - public void clearJNDIBeforeTest() { - if (builder != null) { - builder.clear(); - } - } - - @After + @AfterEach public void killAndHowStateAfterTest() { AndHowCoreTestUtil.destroyAndHow(); } - @After + @AfterEach public void clearJNDIAfterTest() { if (builder != null) { builder.clear(); } } - @After + @AfterEach public void restoreSysPropsAfterTest() { System.setProperties(beforeTestSystemProps); } - @AfterClass + @AfterAll public static void killAndHowStateAfterClass() { AndHowCoreTestUtil.destroyAndHow(); } - @AfterClass + @AfterAll public static void restoreSysPropsAfterClass() { System.setProperties(beforeClassSystemProps); } diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowCoreTestUtil.java b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowCoreTestUtil.java index 19332d95..9ff913d6 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowCoreTestUtil.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowCoreTestUtil.java @@ -105,7 +105,6 @@ public static AndHow setAndHowInstance(AndHow newInstance) { } - public static void forceRebuild(AndHowConfiguration config) { AndHow ahInstance = getAndHowInstance(); diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowReentrantTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowReentrantTest.java new file mode 100644 index 00000000..6a3163e8 --- /dev/null +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowReentrantTest.java @@ -0,0 +1,71 @@ +package org.yarnandtail.andhow; + + +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.TestMethodOrder; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.MethodOrderer; +import org.yarnandtail.andhow.api.AppFatalException; +import org.yarnandtail.andhow.internal.ConstructionProblem; + +/** + * + * Note: The order of the tests is important and is sorted by method name. + * The key reason for this is to test that an InitiationLoopException does not + * bleed over from one test to another. + * @author eeverman + */ +@TestMethodOrder(MethodOrderer.MethodName.class) +public class AndHowReentrantTest extends AndHowCoreTestBase { + + + @Test + public void test_1_AndHowReentrantTest_BadSample_1() { + AndHowConfiguration config = AndHowCoreTestConfig.instance() + .group(AndHowReentrantTest_BadSample_1.class); + + try { + AndHow.instance(config); + fail("This should have blown up"); + } catch (Throwable t) { + assertTrue(t.getCause() instanceof AppFatalException); + AppFatalException afe = (AppFatalException)t.getCause(); + assertEquals(1, afe.getProblems().size()); + assertTrue(afe.getProblems().get(0) instanceof ConstructionProblem.InitiationLoopException); + } + } + + @Test + public void test_2_AndHowReentrantTest_OkSample_1() { + AndHowConfiguration config = AndHowCoreTestConfig.instance() + .group(AndHowReentrantTest_OkSample_1.class); + + + AndHow.instance(config); + + assertEquals("onetwo", AndHowReentrantTest_OkSample_1.getSomeString()); + assertEquals("one", AndHowReentrantTest_OkSample_1.STR_1.getValue()); + assertEquals("two", AndHowReentrantTest_OkSample_1.STR_2.getValue()); + + } + + @Test + public void test_3_AndHowReentrantTest_BadSample_2() { + AndHowConfiguration config = AndHowCoreTestConfig.instance() + .group(AndHowReentrantTest_BadSample_2.class); + + try { + AndHow.instance(config); + fail("This should have blown up"); + } catch (Throwable t) { + assertTrue(t.getCause() instanceof AppFatalException); + AppFatalException afe = (AppFatalException)t.getCause(); + assertEquals(1, afe.getProblems().size()); + assertTrue(afe.getProblems().get(0) instanceof ConstructionProblem.InitiationLoopException); + } + } + + + +} diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowReentrantTest_BadSample_1.java b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowReentrantTest_BadSample_1.java new file mode 100644 index 00000000..8f80a5cd --- /dev/null +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowReentrantTest_BadSample_1.java @@ -0,0 +1,13 @@ +package org.yarnandtail.andhow; + +import org.yarnandtail.andhow.property.*; + +/** + * Demonstrates an invalid reference to a Property value at construction time. + * @author ericeverman + */ +public class AndHowReentrantTest_BadSample_1 { + static final StrProp STR_1 = StrProp.builder().defaultValue("one").build(); + static final StrProp STR_2 = StrProp.builder().defaultValue(STR_1.getValue()).build(); +} + diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowReentrantTest_BadSample_2.java b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowReentrantTest_BadSample_2.java new file mode 100644 index 00000000..6b3f174c --- /dev/null +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowReentrantTest_BadSample_2.java @@ -0,0 +1,18 @@ +package org.yarnandtail.andhow; + +import org.yarnandtail.andhow.property.*; + +/** + * Demonstrates an invalid reference to a Property value at construction time. + * @author ericeverman + */ +public class AndHowReentrantTest_BadSample_2 { + static final StrProp STR_1 = StrProp.builder().defaultValue("one").build(); + static final StrProp STR_2 = StrProp.builder().defaultValue("two").build(); + static final String SOME_STRING; + + static { + SOME_STRING = STR_1.getValue() + STR_2.getValue(); //This should be ok + } +} + diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowReentrantTest_OkSample_1.java b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowReentrantTest_OkSample_1.java new file mode 100644 index 00000000..2f41854b --- /dev/null +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowReentrantTest_OkSample_1.java @@ -0,0 +1,17 @@ +package org.yarnandtail.andhow; + +import org.yarnandtail.andhow.property.*; + +/** + * This should be just fine + * @author ericeverman + */ +public class AndHowReentrantTest_OkSample_1 { + static final StrProp STR_1 = StrProp.builder().defaultValue("one").build(); + static final StrProp STR_2 = StrProp.builder().defaultValue("two").build(); + + static String getSomeString() { + return STR_1.getValue() + STR_2.getValue(); //This should be ok + } +} + diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowTest.java index b8136006..a5779235 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowTest.java @@ -3,7 +3,9 @@ import java.io.File; import java.time.LocalDateTime; import java.util.*; -import org.junit.*; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.api.AppFatalException; import org.yarnandtail.andhow.api.Property; import org.yarnandtail.andhow.internal.*; @@ -12,7 +14,7 @@ import org.yarnandtail.andhow.property.FlagProp; import org.yarnandtail.andhow.property.StrProp; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * This test is a minimal unit teat because this class is not really testable @@ -39,7 +41,7 @@ public static interface RequiredParams { FlagProp FLAG_NULL = FlagProp.builder().mustBeNonNull().build(); } - @Before + @BeforeEach public void setup() throws Exception { configPtGroups.clear(); @@ -62,6 +64,20 @@ public void setup() throws Exception { } + @Test + public void testInitialization() { + Long startTime = System.currentTimeMillis(); + + AndHow.Initialization init = new AndHow.Initialization(); + + Long endTime = System.currentTimeMillis(); + + assertEquals(this.getClass().getName(), init.getStackTrace()[0].getClassName()); + assertEquals("testInitialization", init.getStackTrace()[0].getMethodName()); + assertTrue(startTime <= init.getTimeStamp()); + assertTrue(endTime >= init.getTimeStamp()); + } + @Test public void testIsInitialize() { @@ -84,6 +100,7 @@ public void testCmdLineLoaderUsingClassBaseName() { AndHow.instance(config); + assertTrue(AndHow.getInitializationTrace().length > 0); assertEquals("test", SimpleParams.STR_BOB.getValue()); assertEquals("not_null", SimpleParams.STR_NULL.getValue()); assertEquals(false, SimpleParams.FLAG_TRUE.getValue()); diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowUsageExampleTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowUsageExampleTest.java index d8e123d7..c63d35cd 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowUsageExampleTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHowUsageExampleTest.java @@ -1,10 +1,10 @@ package org.yarnandtail.andhow; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.api.AppFatalException; import org.yarnandtail.andhow.internal.RequirementProblem; import org.yarnandtail.andhow.load.KeyValuePairLoader; @@ -21,7 +21,7 @@ public class AndHowUsageExampleTest extends AndHowCoreTestBase { String[] cmdLineArgsWFullClassName = new String[0]; - @Before + @BeforeEach public void setup() { cmdLineArgsWFullClassName = new String[] { diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHow_AliasInTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHow_AliasInTest.java index 19e24a7b..314d8665 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHow_AliasInTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHow_AliasInTest.java @@ -1,7 +1,7 @@ package org.yarnandtail.andhow; import java.util.List; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.mock.jndi.SimpleNamingContextBuilder; import org.yarnandtail.andhow.api.AppFatalException; import org.yarnandtail.andhow.internal.ConstructionProblem; @@ -10,7 +10,7 @@ import org.yarnandtail.andhow.property.IntProp; import org.yarnandtail.andhow.property.StrProp; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHow_AliasOutTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHow_AliasOutTest.java index f40eb774..a860581c 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/AndHow_AliasOutTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/AndHow_AliasOutTest.java @@ -2,18 +2,14 @@ import java.util.List; -import static org.junit.Assert.*; - -import org.junit.*; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.api.*; import org.yarnandtail.andhow.export.SysPropExporter; -import org.yarnandtail.andhow.util.AndHowUtil; import org.yarnandtail.andhow.internal.ConstructionProblem; -import org.yarnandtail.andhow.internal.NameAndProperty; import org.yarnandtail.andhow.property.IntProp; import org.yarnandtail.andhow.property.StrProp; -import org.yarnandtail.andhow.util.NameUtil; /** * diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/PropertyValueTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/PropertyValueTest.java index 6b99d4bb..771214b4 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/PropertyValueTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/PropertyValueTest.java @@ -1,9 +1,10 @@ package org.yarnandtail.andhow; -import org.junit.Test; +import org.junit.jupiter.api.Test; +import org.yarnandtail.andhow.api.ParsingException; import org.yarnandtail.andhow.property.StrProp; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; import static org.yarnandtail.andhow.SimpleParams.*; /** @@ -140,9 +141,11 @@ public void testGetValue() { assertEquals(SPV5.getValue(), "abc"); } - @Test(expected = RuntimeException.class) + @Test public void testConstructor() { - PropertyValue NULL_PROP = new PropertyValue((StrProp)null, null); + assertThrows(RuntimeException.class, () -> + new PropertyValue((StrProp)null, null) + ); } } diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/StdConfigGetterAndSetterTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/StdConfigGetterAndSetterTest.java new file mode 100644 index 00000000..63b1da97 --- /dev/null +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/StdConfigGetterAndSetterTest.java @@ -0,0 +1,431 @@ +/* + */ +package org.yarnandtail.andhow; + +import java.util.*; + +import org.junit.jupiter.api.Test; +import org.yarnandtail.andhow.StdConfig.StdConfigImpl; +import org.yarnandtail.andhow.api.*; +import org.yarnandtail.andhow.internal.ValidatedValuesWithContextMutable; +import org.yarnandtail.andhow.load.*; +import org.yarnandtail.andhow.load.std.*; +import org.yarnandtail.andhow.name.CaseInsensitiveNaming; +import org.yarnandtail.andhow.property.DblProp; +import org.yarnandtail.andhow.property.LngProp; +import org.yarnandtail.andhow.property.StrProp; + +import static org.junit.jupiter.api.Assertions.*; +import static org.hamcrest.Matchers.*; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * + * @author ericeverman + */ +public class StdConfigGetterAndSetterTest { + + public StdConfigGetterAndSetterTest() { + } + + /** + * Test of getNamingStrategy method, of class BaseConfig. + */ + @Test + public void testGetNamingStrategy() { + StdConfigImpl std = StdConfig.instance(); + assertTrue(std.getNamingStrategy() instanceof CaseInsensitiveNaming); + } + + /** + * Test of buildLoaders method, of class BaseConfig. + */ + @Test + public void testBuildLoadersWithoutAnyChanges() { + StdConfigImpl std = StdConfig.instance(); + List loaders = std.buildLoaders(); + assertEquals(7, loaders.size()); + assertEquals(StdFixedValueLoader.class, loaders.get(0).getClass()); + assertEquals(StdMainStringArgsLoader.class, loaders.get(1).getClass()); + assertEquals(StdSysPropLoader.class, loaders.get(2).getClass()); + assertEquals(StdEnvVarLoader.class, loaders.get(3).getClass()); + assertEquals(StdJndiLoader.class, loaders.get(4).getClass()); + assertEquals(StdPropFileOnFilesystemLoader.class, loaders.get(5).getClass()); + assertEquals(StdPropFileOnClasspathLoader.class, loaders.get(6).getClass()); + } + + @Test + public void testBuildLoadersWithCustomListOfStandardLoaders() { + StdConfigImpl std = StdConfig.instance(); + + List> newLoaders = new ArrayList(); + newLoaders.add(StdFixedValueLoader.class); + newLoaders.add(StdJndiLoader.class); + + std.setStandardLoaders(newLoaders); + List loaders = std.buildLoaders(); + assertEquals(2, loaders.size()); + assertEquals(StdFixedValueLoader.class, loaders.get(0).getClass()); + assertEquals(StdJndiLoader.class, loaders.get(1).getClass()); + + std.setStandardLoaders(std.getDefaultLoaderList()); + loaders = std.buildLoaders(); + assertEquals(7, loaders.size()); + assertEquals(StdFixedValueLoader.class, loaders.get(0).getClass()); + assertEquals(StdPropFileOnClasspathLoader.class, loaders.get(6).getClass()); + + std.setStandardLoaders(StdSysPropLoader.class, StdPropFileOnFilesystemLoader.class); + loaders = std.buildLoaders(); + assertEquals(2, loaders.size()); + assertEquals(StdSysPropLoader.class, loaders.get(0).getClass()); + assertEquals(StdPropFileOnFilesystemLoader.class, loaders.get(1).getClass()); + } + + @Test + public void testBuildLoadersWithInsertingLoadersBeforeAndAfter() { + StdConfigImpl std = StdConfig.instance(); + + Loader loader1 = new MapLoader(); + Loader loader2 = new KeyValuePairLoader(); + Loader loader3 = new PropFileOnClasspathLoader(); + Loader loader4 = new PropFileOnFilesystemLoader(); + Loader loader5 = new FixedValueLoader(); + Loader loader6 = new MapLoader(); + Loader loader7 = new KeyValuePairLoader(); + Loader loader8 = new PropFileOnClasspathLoader(); + Loader loader9 = new PropFileOnFilesystemLoader(); + + //At the beginning of the list + std.insertLoaderBefore(StdFixedValueLoader.class, loader1); + std.insertLoaderBefore(StdFixedValueLoader.class, loader2); + std.insertLoaderAfter(StdFixedValueLoader.class, loader3); + std.insertLoaderAfter(StdFixedValueLoader.class, loader4); + std.insertLoaderBefore(StdMainStringArgsLoader.class, loader5); + + //At the end of the list + std.insertLoaderBefore(StdPropFileOnFilesystemLoader.class, loader6); + std.insertLoaderAfter(StdPropFileOnFilesystemLoader.class, loader7); + std.insertLoaderBefore(StdPropFileOnClasspathLoader.class, loader8); + std.insertLoaderAfter(StdPropFileOnClasspathLoader.class, loader9); + + List loaders = std.buildLoaders(); + assertEquals(16, loaders.size()); + assertEquals(loader1, loaders.get(0)); + assertEquals(loader2, loaders.get(1)); + assertEquals(StdFixedValueLoader.class, loaders.get(2).getClass()); + assertEquals(loader3, loaders.get(3)); + assertEquals(loader4, loaders.get(4)); + assertEquals(loader5, loaders.get(5)); + assertEquals(StdMainStringArgsLoader.class, loaders.get(6).getClass()); + assertEquals(StdSysPropLoader.class, loaders.get(7).getClass()); + assertEquals(StdEnvVarLoader.class, loaders.get(8).getClass()); + assertEquals(StdJndiLoader.class, loaders.get(9).getClass()); + assertEquals(loader6, loaders.get(10)); + assertEquals(StdPropFileOnFilesystemLoader.class, loaders.get(11).getClass()); + assertEquals(loader7, loaders.get(12)); + assertEquals(loader8, loaders.get(13)); + assertEquals(StdPropFileOnClasspathLoader.class, loaders.get(14).getClass()); + assertEquals(loader9, loaders.get(15)); + } + + @Test + public void FixedValuesBasedOnPropertiesTest() { + MyStdConfig config = new MyStdConfig(); + + StrProp MY_STR_1 = StrProp.builder().build(); + LngProp MY_LNG_2 = LngProp.builder().build(); + DblProp MY_DBL_3 = DblProp.builder().build(); + + config.addFixedValue(MY_STR_1, "ABC"); + config.addFixedValue(MY_LNG_2, 23L); + + assertEquals(2, config.getFixedValues().size()); + assertTrue(containsPropertyAndValue(config.getFixedValues(), MY_STR_1, "ABC")); + assertTrue(containsPropertyAndValue(config.getFixedValues(), MY_LNG_2, 23L)); + + //Try to add a duplicate property + assertThrows(IllegalArgumentException.class, () -> { + config.addFixedValue(MY_STR_1, "ZZZ"); + }); + + assertEquals("ABC", + config.getFixedValues().stream().filter(p -> p.getProperty().equals(MY_STR_1)).findFirst().get() + .getValue().toString(), + "The value set for this Property should be unchanged"); + + //Try to add a null property + assertThrows(IllegalArgumentException.class, () -> { + config.addFixedValue((Property)null, "ZZZ"); + }); + + assertEquals(2, config.getFixedValues().size(), "Still should have two values"); + + //Try removing some + config.removeFixedValue(MY_STR_1); + assertEquals(1, config.getFixedValues().size()); + assertTrue(containsPropertyAndValue(config.getFixedValues(), MY_LNG_2, 23L)); + + //it should be OK and a no-op to attempt to remove a non-existing property + //...but how would this ever happen?? + config.removeFixedValue(MY_DBL_3); + assertEquals(1, config.getFixedValues().size()); + + config.removeFixedValue(MY_LNG_2); + assertEquals(0, config.getFixedValues().size()); + } + + + @Test + public void FixedValuesBasedOnNamesTest() { + MyStdConfig config = new MyStdConfig(); + + //These properties don't really exist - no checking is done until loading, when + //the Loader attempts to match up the name w/ a property. For now this just tests + //the logic in StdConfig. + config.addFixedValue("MY_STR_1", "ABC"); + config.addFixedValue("MY_LNG_2", 23L); + + assertEquals(2, config.getFixedKeyObjectPairValues().size()); + assertTrue(containsNameAndValue(config.getFixedKeyObjectPairValues(), "MY_STR_1", "ABC")); + assertTrue(containsNameAndValue(config.getFixedKeyObjectPairValues(), "MY_LNG_2", 23L)); + + //Try to add a duplicate property + assertThrows(IllegalArgumentException.class, () -> { + config.addFixedValue("MY_STR_1", "ZZZ"); + }); + + assertEquals("ABC", + config.getFixedKeyObjectPairValues().stream().filter(k -> k.getName().equals("MY_STR_1")).findFirst().get() + .getValue().toString(), + "The value set for this Property should be unchanged"); + + //Try to add a null property name + assertThrows(IllegalArgumentException.class, () -> { + config.addFixedValue((String)null, "ZZZ"); + }); + + //Try to add an empty (all space) property name + assertThrows(IllegalArgumentException.class, () -> { + config.addFixedValue(" ", "ZZZ"); + }); + + assertEquals(2, config.getFixedKeyObjectPairValues().size(), "Still should have two values"); + + //Try removing some + config.removeFixedValue("MY_STR_1"); + assertEquals(1, config.getFixedKeyObjectPairValues().size()); + assertTrue(containsNameAndValue(config.getFixedKeyObjectPairValues(), "MY_LNG_2", 23L)); + + //it should be OK and a no-op to attempt to remove a non-existing property, or a property not in the list. + config.removeFixedValue("MY_DBL_3"); + assertEquals(1, config.getFixedKeyObjectPairValues().size()); + + config.removeFixedValue("MY_LNG_2"); + assertEquals(0, config.getFixedKeyObjectPairValues().size()); + } + + @Test + public void setCmdLineArgsTest() { + MyStdConfig config = new MyStdConfig(); + + String[] args = new String[] {"abc=123", "xyz='456"}; + config.setCmdLineArgs(args); + assertThat(config.getCmdLineArgs().toArray(), arrayContainingInAnyOrder(args)); + + Object o = TestUtil.getField(config.buildStdMainStringArgsLoader(), "keyValuePairs"); + List kvps = (List)o; + + assertEquals(2, kvps.size()); + assertThat(kvps.toArray(), arrayContainingInAnyOrder(args)); + } + + @Test + public void setEnvironmentPropertiesTest() { + MyStdConfig config = new MyStdConfig(); + + Map envVars = new HashMap<>(); + envVars.put("abc", "123"); + envVars.put("xyz", "456"); + + config.setEnvironmentProperties(envVars); + + assertEquals(2, config.getEnvironmentProperties().size()); + assertTrue(envVars.equals(config.getEnvironmentProperties())); + assertFalse(envVars == config.getEnvironmentProperties(), "Should be disconnected object"); + + //Now try setting new values - they should replace the old + Map envVars2 = new HashMap<>(); + envVars2.put("bob", "bob_val"); + config.setEnvironmentProperties(envVars2); + + assertEquals(1, config.getEnvironmentProperties().size()); + assertTrue(envVars2.equals(config.getEnvironmentProperties())); + + //Now set to null + config.setEnvironmentProperties(null); + assertNull(config.getEnvironmentProperties()); + + } + + @Test + public void setClasspathPropFilePathViaStringTest() throws Exception { + MyStdConfig config = new MyStdConfig(); + ValidatedValuesWithContextMutable vvs = new ValidatedValuesWithContextMutable(); + Class vvsClass = ValidatedValuesWithContext.class; //class of getEffectivePath argument + + assertNull(config.getClasspathPropFileProp()); + assertNull(config.getClasspathPropFilePath()); + assertEquals("/andhow.properties", + TestUtil.stringMethod(config.buildStdPropFileOnClasspathLoader(), "getEffectivePath", vvs, vvsClass), + "Loader should see the default value"); + + config.setClasspathPropFilePath("/andhow.test.properties"); + assertEquals("/andhow.test.properties", config.getClasspathPropFilePath()); + assertNull(config.getClasspathPropFileProp()); + assertEquals("/andhow.test.properties", + TestUtil.stringMethod(config.buildStdPropFileOnClasspathLoader(), "getEffectivePath", vvs, vvsClass), + "Loader should see this configured value"); + + config.setClasspathPropFilePath("/andhow.test.props"); + assertEquals("/andhow.test.props", config.getClasspathPropFilePath()); + assertEquals("/andhow.test.props", + TestUtil.stringMethod(config.buildStdPropFileOnClasspathLoader(), "getEffectivePath", vvs, vvsClass), + "Loader should see this configured value"); + + assertThrows(IllegalArgumentException.class, () -> { + config.setClasspathPropFilePath("andhow.test.props"); + }, "paths containing dots must start w/ a slash"); + + assertEquals("/andhow.test.props", config.getClasspathPropFilePath(), "Should be unchanged"); + + config.setClasspathPropFilePath("/org/comcorp/project/andhow.test.properties"); + assertEquals("/org/comcorp/project/andhow.test.properties", config.getClasspathPropFilePath()); + assertEquals("/org/comcorp/project/andhow.test.properties", + TestUtil.stringMethod(config.buildStdPropFileOnClasspathLoader(), "getEffectivePath", vvs, vvsClass), + "Loader should see this configured value"); + + config.setClasspathPropFilePath((String)null); + assertNull(config.getClasspathPropFilePath()); + assertEquals("/andhow.properties", + TestUtil.stringMethod(config.buildStdPropFileOnClasspathLoader(), "getEffectivePath", vvs, vvsClass), + "Loader should see the default value"); + + } + + @Test + public void setClasspathPropFilePathViaStrPropTest() throws Exception { + StrProp MY_PATH_PROPERTY = StrProp.builder().build(); + + MyStdConfig config = new MyStdConfig(); + + ValidatedValue validatedValue = new ValidatedValue(MY_PATH_PROPERTY, "/my.prop.file"); + List vvList = new ArrayList<>(); + vvList.add(validatedValue); + + LoaderValues loaderValues = new LoaderValues(new FixedValueLoader(), vvList, ProblemList.EMPTY_PROBLEM_LIST); + ValidatedValuesWithContextMutable validatedValues = new ValidatedValuesWithContextMutable(); + validatedValues.addValues(loaderValues); + Class vvsClass = ValidatedValuesWithContext.class; //class of getEffectivePath argument + + config.setClasspathPropFilePath(MY_PATH_PROPERTY); + assertNull(config.getClasspathPropFilePath()); + assertEquals(MY_PATH_PROPERTY, config.getClasspathPropFileProp()); + assertEquals("/my.prop.file", + TestUtil.stringMethod(config.buildStdPropFileOnClasspathLoader(), "getEffectivePath", validatedValues, vvsClass), + "Loader should see this configured value"); + + config.setClasspathPropFilePath((StrProp)null); + assertNull(config.getClasspathPropFilePath()); + assertNull(config.getClasspathPropFileProp()); + assertEquals("/andhow.properties", + TestUtil.stringMethod(config.buildStdPropFileOnClasspathLoader(), "getEffectivePath", validatedValues, vvsClass), + "Loader should revert to default"); + } + + @Test + public void setClasspathPropFilePathInteractionOfStringAndStrPropTest() { + StrProp MY_PATH_PROPERTY = StrProp.builder().build(); + MyStdConfig config = new MyStdConfig(); + + config.setClasspathPropFilePath(MY_PATH_PROPERTY); + assertThrows(IllegalArgumentException.class, () -> { + config.setClasspathPropFilePath("/some.other.path"); + }, "Can't set via String and StrProp at the same time"); + assertNull(config.getClasspathPropFilePath(), "Should not have set the value due to error"); + + config.setClasspathPropFilePath((StrProp)null); + config.setClasspathPropFilePath("/some.other.path"); //now its OK + assertEquals("/some.other.path", config.getClasspathPropFilePath()); + + assertThrows(IllegalArgumentException.class, () -> { + config.setClasspathPropFilePath(MY_PATH_PROPERTY); + }, "String version is set, so now this one causes the error"); + assertNull(config.getClasspathPropFileProp(), "Should not have set the value due to error"); + + } + + @Test void classpathPropertiesRequiredOrNotTest() { + MyStdConfig config = new MyStdConfig(); + + assertFalse(config.getClasspathPropFileRequired(), "False should be the default"); + assertFalse(config.buildStdPropFileOnClasspathLoader().isMissingFileAProblem(), "False should be the default"); + + config.classpathPropertiesRequired(); + + assertTrue(config.getClasspathPropFileRequired()); + assertTrue(config.buildStdPropFileOnClasspathLoader().isMissingFileAProblem()); + } + + @Test void filesystemPropertiesRequiredOrNotTest() { + MyStdConfig config = new MyStdConfig(); + + assertFalse(config.getFilesystemPropFileRequired(), "False should be the default"); + assertFalse(config.buildStdPropFileOnFilesystemLoader().isMissingFileAProblem(), "False should be the default"); + + config.filesystemPropFileRequired(); + + assertTrue(config.getFilesystemPropFileRequired()); + assertTrue(config.buildStdPropFileOnFilesystemLoader().isMissingFileAProblem()); + } + + boolean containsPropertyAndValue(List propertyValues, Property property, T value) { + PropertyValue pv = propertyValues.stream().filter(p -> p.getProperty().equals(property)).findFirst().get(); + return pv != null && pv.getValue().equals(value); + } + + boolean containsNameAndValue(List keyObjectPairs, String name, Object value) { + KeyObjectPair kop = keyObjectPairs.stream().filter(p -> p.getName().equals(name)).findFirst().get(); + return kop != null && kop.getValue().equals(value); + } + + /** + * Custom StdConfig class that has access methods for fields not otherwise accessable. + */ + public static final class MyStdConfig extends StdConfig.StdConfigAbstract { + public List getFixedValues() { + return _fixedVals; + } + + public List getFixedKeyObjectPairValues() { + return _fixedKeyObjectPairVals; + } + + public List getCmdLineArgs() { + return _cmdLineArgs; + } + + public Map getEnvironmentProperties() { + return envProperties; + } + + public String getClasspathPropFilePath() { return classpathPropFilePathStr; } + + public StrProp getClasspathPropFileProp() { return this.classpathPropFilePathProp; } + + public boolean getClasspathPropFileRequired() { return _missingClasspathPropFileAProblem; } + + public boolean getFilesystemPropFileRequired() { return _missingFilesystemPropFileAProblem; } + } + +} diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/StdConfigSimulatedAppTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/StdConfigSimulatedAppTest.java new file mode 100644 index 00000000..e38c12d6 --- /dev/null +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/StdConfigSimulatedAppTest.java @@ -0,0 +1,267 @@ +package org.yarnandtail.andhow; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.mock.jndi.SimpleNamingContextBuilder; +import org.yarnandtail.andhow.api.AppFatalException; +import org.yarnandtail.andhow.api.Property; +import org.yarnandtail.andhow.internal.LoaderProblem; +import org.yarnandtail.andhow.internal.ValueProblem; +import org.yarnandtail.andhow.load.KeyValuePairLoader; +import org.yarnandtail.andhow.property.FlagProp; +import org.yarnandtail.andhow.property.IntProp; +import org.yarnandtail.andhow.property.StrProp; +import static org.yarnandtail.andhow.load.KeyValuePairLoader.KVP_DELIMITER; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * + * @author eeverman + */ +public class StdConfigSimulatedAppTest extends AndHowCoreTestBase { + + private static final String GROUP_PATH = "org.yarnandtail.andhow.StdConfigSimulatedAppTest.SampleRestClientGroup"; + private static final String CLASSPATH_BEGINNING = "/org/yarnandtail/andhow/StdConfigSimulatedAppTest."; + + + @Test + public void testAllValuesAreSetViaCmdLineArgAndPropFile() { + + //Prop name case ignored + String[] cmdLineArgs = new String[] { + GROUP_PATH + ".classpath_prop_file" + KVP_DELIMITER + CLASSPATH_BEGINNING + "all.props.speced.properties" + }; + + AndHowConfiguration config = AndHowCoreTestConfig.instance() + .group(SampleRestClientGroup.class) + .setCmdLineArgs(cmdLineArgs) + .setClasspathPropFilePath(SampleRestClientGroup.CLASSPATH_PROP_FILE) + .classpathPropertiesRequired(); + + AndHow.instance(config); + + assertEquals("/org/yarnandtail/andhow/StdConfigSimulatedAppTest.all.props.speced.properties", + SampleRestClientGroup.CLASSPATH_PROP_FILE.getValue()); + assertAllPointsSpecedValues("testAllValuesAreSetViaCmdLineArgAndPropFile"); + } + + @Test + public void testAllValuesAreSetViaFixedPropertyValueAndPropFile() { + + AndHowConfiguration config = AndHowCoreTestConfig.instance() + .group(SampleRestClientGroup.class) + .addFixedValue(SampleRestClientGroup.CLASSPATH_PROP_FILE, + CLASSPATH_BEGINNING + "all.props.speced.properties") //set via FixedValue (PropertyValue) + .setClasspathPropFilePath(SampleRestClientGroup.CLASSPATH_PROP_FILE) + .classpathPropertiesRequired(); + + AndHow.instance(config); + + assertEquals("/org/yarnandtail/andhow/StdConfigSimulatedAppTest.all.props.speced.properties", + SampleRestClientGroup.CLASSPATH_PROP_FILE.getValue()); + assertAllPointsSpecedValues("testAllValuesAreSetViaFixedPropertyValueAndPropFile"); + } + + @Test + public void testAllValuesAreSetViaFixedPropertyValueAndPropFileOverridingWithFixedPropertyValue() { + + AndHowConfiguration config = AndHowCoreTestConfig.instance() + .group(SampleRestClientGroup.class) + .addFixedValue(SampleRestClientGroup.CLASSPATH_PROP_FILE, + CLASSPATH_BEGINNING + "all.props.speced.properties") //set via FixedValue (PropertyValue) + .addFixedValue(SampleRestClientGroup.REST_PORT, 99) //Override value in prop file + .setClasspathPropFilePath(SampleRestClientGroup.CLASSPATH_PROP_FILE) + .classpathPropertiesRequired(); + + AndHow.instance(config); + + assertEquals(" Big App ", SampleRestClientGroup.APP_NAME.getValue()); //Just check one prop file val + assertEquals(99, SampleRestClientGroup.REST_PORT.getValue(), "Fixed value should override prop file"); + } + + @Test + public void testAllValuesAreSetViaFixedKeyObjectPairAndPropFile() { + + AndHowConfiguration config = AndHowCoreTestConfig.instance() + .group(SampleRestClientGroup.class) + .addFixedValue(GROUP_PATH + ".classpath_prop_file", /* case ignored */ + CLASSPATH_BEGINNING + "all.props.speced.properties") //set via FixedValue (KeyObjectPair) + .setClasspathPropFilePath(SampleRestClientGroup.CLASSPATH_PROP_FILE) + .classpathPropertiesRequired(); + + AndHow.instance(config); + + assertEquals("/org/yarnandtail/andhow/StdConfigSimulatedAppTest.all.props.speced.properties", + SampleRestClientGroup.CLASSPATH_PROP_FILE.getValue()); + assertAllPointsSpecedValues("testAllValuesAreSetViaFixedKeyObjectPairAndPropFile"); + } + + @Test + public void testAllValuesAreSetViaFixedKeyObjectPairAndPropFileOverridingWithFixedKeyObjectPair() { + + AndHowConfiguration config = AndHowCoreTestConfig.instance() + .group(SampleRestClientGroup.class) + .addFixedValue(GROUP_PATH + ".classpath_prop_file", /* case ignored */ + CLASSPATH_BEGINNING + "all.props.speced.properties") //set via FixedValue (KeyObjectPair) + .addFixedValue(GROUP_PATH + ".REST_PORT", 98) //Override value in prop file + .setClasspathPropFilePath(SampleRestClientGroup.CLASSPATH_PROP_FILE) + .classpathPropertiesRequired(); + + AndHow.instance(config); + + assertEquals(" Big App ", SampleRestClientGroup.APP_NAME.getValue()); //Just check one prop file val + assertEquals(98, SampleRestClientGroup.REST_PORT.getValue(), "Fixed value should override prop file"); + } + + @Test + public void testAllValuesAreSetViaEnvVarAndPropFile() { + + Map envvars = new HashMap(); + envvars.put( + GROUP_PATH + ".classpath_prop_file", /* case ignored */ + CLASSPATH_BEGINNING + "all.props.speced.properties" + ); + + AndHowConfiguration config = AndHowCoreTestConfig.instance() + .group(SampleRestClientGroup.class) + .setEnvironmentProperties(envvars) + .setClasspathPropFilePath(SampleRestClientGroup.CLASSPATH_PROP_FILE) + .classpathPropertiesRequired(); + + AndHow.instance(config); + + assertEquals("/org/yarnandtail/andhow/StdConfigSimulatedAppTest.all.props.speced.properties", + SampleRestClientGroup.CLASSPATH_PROP_FILE.getValue()); + assertAllPointsSpecedValues("testAllValuesAreSetViaEnvVarAndPropFile"); + } + + @Test + public void testAllValuesAreSetViaSystemPropertiesAndPropFile() { + + Properties props = new Properties(); + props.put( + GROUP_PATH + ".classpath_prop_file", /* case ignored */ + CLASSPATH_BEGINNING + "all.props.speced.properties" + ); + + AndHowConfiguration config = AndHowCoreTestConfig.instance() + .group(SampleRestClientGroup.class) + .setSystemProperties(props) + .setClasspathPropFilePath(SampleRestClientGroup.CLASSPATH_PROP_FILE) + .classpathPropertiesRequired(); + + AndHow.instance(config); + + assertEquals("/org/yarnandtail/andhow/StdConfigSimulatedAppTest.all.props.speced.properties", + SampleRestClientGroup.CLASSPATH_PROP_FILE.getValue()); + assertAllPointsSpecedValues("testAllValuesAreSetViaSystemPropertiesAndPropFile"); + } + + /** + * Asserts each value to match the values spec'ed in the StdConfigSimulatedAppTest.all.props.speced.properties file. + */ + void assertAllPointsSpecedValues(String calledFromTestName) { + String failDesc = "assertion failure in the test named '" + calledFromTestName + "'"; + + assertEquals(" Big App ", SampleRestClientGroup.APP_NAME.getValue(), failDesc); + assertEquals("aquarius.usgs.gov", SampleRestClientGroup.REST_HOST.getValue(), failDesc); + assertEquals(new Integer(8080), SampleRestClientGroup.REST_PORT.getValue(), failDesc); + assertEquals("doquery/", SampleRestClientGroup.REST_SERVICE_NAME.getValue(), failDesc); + assertEquals("abc123", SampleRestClientGroup.AUTH_KEY.getValue(), failDesc); + assertEquals(new Integer(4), SampleRestClientGroup.RETRY_COUNT.getValue(), failDesc); + assertFalse(SampleRestClientGroup.REQUEST_META_DATA.getValue(), failDesc); + assertTrue(SampleRestClientGroup.REQUEST_SUMMARY_DATA.getValue(), failDesc); + } + + @Test + public void testMinimumPropsAreSetViaCmdLineArgAndPropFile() { + + String[] cmdLineArgs = new String[] { + GROUP_PATH + ".CLASSPATH_PROP_FILE" + KVP_DELIMITER + CLASSPATH_BEGINNING + "minimum.props.speced.properties" + }; + + AndHowConfiguration config = AndHowCoreTestConfig.instance() + .group(SampleRestClientGroup.class) + .setCmdLineArgs(cmdLineArgs) + .setClasspathPropFilePath(SampleRestClientGroup.CLASSPATH_PROP_FILE) + .classpathPropertiesRequired(); + + AndHow.instance(config); + + assertEquals("/org/yarnandtail/andhow/StdConfigSimulatedAppTest.minimum.props.speced.properties", + SampleRestClientGroup.CLASSPATH_PROP_FILE.getValue()); + assertEquals("aquarius.usgs.gov", SampleRestClientGroup.REST_HOST.getValue()); + assertEquals(new Integer(8080), SampleRestClientGroup.REST_PORT.getValue()); + assertEquals("query/", SampleRestClientGroup.REST_SERVICE_NAME.getValue()); //a default value + assertEquals("abc123", SampleRestClientGroup.AUTH_KEY.getValue()); + assertEquals(new Integer(2), SampleRestClientGroup.RETRY_COUNT.getValue()); //a default + assertTrue(SampleRestClientGroup.REQUEST_META_DATA.getValue()); //a default + assertFalse(SampleRestClientGroup.REQUEST_SUMMARY_DATA.getValue()); //a default + + } + + @Test + public void testInvalidValuesViaCmdLineArgAndPropFile() throws Exception { + + SimpleNamingContextBuilder jndi = getJndi(); + jndi.activate(); + + String[] cmdLineArgs = new String[] { + GROUP_PATH + ".CLASSPATH_PROP_FILE" + KVP_DELIMITER + CLASSPATH_BEGINNING + "invalid.properties" + }; + + try { + + //Error expected b/c some values are invalid + AndHowConfiguration config = AndHowCoreTestConfig.instance() + .group(SampleRestClientGroup.class) + .setCmdLineArgs(cmdLineArgs) + .setClasspathPropFilePath(SampleRestClientGroup.CLASSPATH_PROP_FILE) + .classpathPropertiesRequired(); + + AndHow.instance(config); + + } catch (AppFatalException e) { + + //Value Problems (validation) + //Due to loading from a prop file, the order of the file is not preserved, + //so we cannot know the order that problems were encountered. + ArrayList> expectedProblemPoints = new ArrayList(); + expectedProblemPoints.add(SampleRestClientGroup.REST_HOST); + expectedProblemPoints.add(SampleRestClientGroup.REST_PORT); + expectedProblemPoints.add(SampleRestClientGroup.REST_SERVICE_NAME); + + assertEquals(3, e.getProblems().filter(ValueProblem.class).size()); + assertTrue(expectedProblemPoints.contains(e.getProblems().filter(ValueProblem.class).get(0).getBadValueCoord().getProperty())); + assertTrue(expectedProblemPoints.contains(e.getProblems().filter(ValueProblem.class).get(1).getBadValueCoord().getProperty())); + assertTrue(expectedProblemPoints.contains(e.getProblems().filter(ValueProblem.class).get(2).getBadValueCoord().getProperty())); + + // + // Loader problems + assertEquals(1, e.getProblems().filter(LoaderProblem.class).size()); + assertEquals(SampleRestClientGroup.RETRY_COUNT, e.getProblems().filter(LoaderProblem.class).get(0).getBadValueCoord().getProperty()); + } + + + } + + interface SampleRestClientGroup { + + StrProp CLASSPATH_PROP_FILE = StrProp.builder().desc("Classpath location of a properties file w/ props").build(); + StrProp APP_NAME = StrProp.builder().aliasIn("app.name").aliasIn("app_name").build(); + StrProp REST_HOST = StrProp.builder().mustMatchRegex(".*\\.usgs\\.gov") .mustBeNonNull().build(); + IntProp REST_PORT = IntProp.builder().mustBeNonNull().mustBeGreaterThanOrEqualTo(80).mustBeLessThan(10000).build(); + StrProp REST_SERVICE_NAME = StrProp.builder().defaultValue("query/").mustEndWith("/").build(); + StrProp AUTH_KEY = StrProp.builder().mustBeNonNull().build(); + IntProp RETRY_COUNT = IntProp.builder().defaultValue(2).build(); + FlagProp REQUEST_META_DATA = FlagProp.builder().defaultValue(true).build(); + FlagProp REQUEST_SUMMARY_DATA = FlagProp.builder().build(); + } + +} diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/StdConfigTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/StdConfigTest.java deleted file mode 100644 index 4a0e615d..00000000 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/StdConfigTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - */ -package org.yarnandtail.andhow; - -import java.util.ArrayList; -import java.util.List; -import org.junit.Test; -import org.yarnandtail.andhow.StdConfig.StdConfigImpl; -import org.yarnandtail.andhow.api.Loader; -import org.yarnandtail.andhow.api.StandardLoader; -import org.yarnandtail.andhow.load.*; -import org.yarnandtail.andhow.load.std.*; -import org.yarnandtail.andhow.name.CaseInsensitiveNaming; - -import static org.junit.Assert.*; - -/** - * - * @author ericeverman - */ -public class StdConfigTest { - - public StdConfigTest() { - } - - /** - * Test of getNamingStrategy method, of class BaseConfig. - */ - @Test - public void testGetNamingStrategy() { - StdConfigImpl std = StdConfig.instance(); - assertTrue(std.getNamingStrategy() instanceof CaseInsensitiveNaming); - } - - /** - * Test of buildLoaders method, of class BaseConfig. - */ - @Test - public void testBuildLoadersWithoutAnyChanges() { - StdConfigImpl std = StdConfig.instance(); - List loaders = std.buildLoaders(); - assertEquals(7, loaders.size()); - assertEquals(StdFixedValueLoader.class, loaders.get(0).getClass()); - assertEquals(StdMainStringArgsLoader.class, loaders.get(1).getClass()); - assertEquals(StdSysPropLoader.class, loaders.get(2).getClass()); - assertEquals(StdEnvVarLoader.class, loaders.get(3).getClass()); - assertEquals(StdJndiLoader.class, loaders.get(4).getClass()); - assertEquals(StdPropFileOnFilesystemLoader.class, loaders.get(5).getClass()); - assertEquals(StdPropFileOnClasspathLoader.class, loaders.get(6).getClass()); - } - - @Test - public void testBuildLoadersWithCustomListOfStandardLoaders() { - StdConfigImpl std = StdConfig.instance(); - - List> newLoaders = new ArrayList(); - newLoaders.add(StdFixedValueLoader.class); - newLoaders.add(StdJndiLoader.class); - - std.setStandardLoaders(newLoaders); - List loaders = std.buildLoaders(); - assertEquals(2, loaders.size()); - assertEquals(StdFixedValueLoader.class, loaders.get(0).getClass()); - assertEquals(StdJndiLoader.class, loaders.get(1).getClass()); - - std.setStandardLoaders(BaseConfig.getDefaultLoaderList()); - loaders = std.buildLoaders(); - assertEquals(7, loaders.size()); - assertEquals(StdFixedValueLoader.class, loaders.get(0).getClass()); - assertEquals(StdPropFileOnClasspathLoader.class, loaders.get(6).getClass()); - - std.setStandardLoaders(StdSysPropLoader.class, StdPropFileOnFilesystemLoader.class); - loaders = std.buildLoaders(); - assertEquals(2, loaders.size()); - assertEquals(StdSysPropLoader.class, loaders.get(0).getClass()); - assertEquals(StdPropFileOnFilesystemLoader.class, loaders.get(1).getClass()); - } - - @Test - public void testBuildLoadersWithInsertingLoadersBeforeAndAfter() { - StdConfigImpl std = StdConfig.instance(); - - Loader loader1 = new MapLoader(); - Loader loader2 = new KeyValuePairLoader(); - Loader loader3 = new PropFileOnClasspathLoader(); - Loader loader4 = new PropFileOnFilesystemLoader(); - Loader loader5 = new FixedValueLoader(); - Loader loader6 = new MapLoader(); - Loader loader7 = new KeyValuePairLoader(); - Loader loader8 = new PropFileOnClasspathLoader(); - Loader loader9 = new PropFileOnFilesystemLoader(); - - //At the beginning of the list - std.insertLoaderBefore(StdFixedValueLoader.class, loader1); - std.insertLoaderBefore(StdFixedValueLoader.class, loader2); - std.insertLoaderAfter(StdFixedValueLoader.class, loader3); - std.insertLoaderAfter(StdFixedValueLoader.class, loader4); - std.insertLoaderBefore(StdMainStringArgsLoader.class, loader5); - - //At the end of the list - std.insertLoaderBefore(StdPropFileOnFilesystemLoader.class, loader6); - std.insertLoaderAfter(StdPropFileOnFilesystemLoader.class, loader7); - std.insertLoaderBefore(StdPropFileOnClasspathLoader.class, loader8); - std.insertLoaderAfter(StdPropFileOnClasspathLoader.class, loader9); - - List loaders = std.buildLoaders(); - assertEquals(16, loaders.size()); - assertEquals(loader1, loaders.get(0)); - assertEquals(loader2, loaders.get(1)); - assertEquals(StdFixedValueLoader.class, loaders.get(2).getClass()); - assertEquals(loader3, loaders.get(3)); - assertEquals(loader4, loaders.get(4)); - assertEquals(loader5, loaders.get(5)); - assertEquals(StdMainStringArgsLoader.class, loaders.get(6).getClass()); - assertEquals(StdSysPropLoader.class, loaders.get(7).getClass()); - assertEquals(StdEnvVarLoader.class, loaders.get(8).getClass()); - assertEquals(StdJndiLoader.class, loaders.get(9).getClass()); - assertEquals(loader6, loaders.get(10)); - assertEquals(StdPropFileOnFilesystemLoader.class, loaders.get(11).getClass()); - assertEquals(loader7, loaders.get(12)); - assertEquals(loader8, loaders.get(13)); - assertEquals(StdPropFileOnClasspathLoader.class, loaders.get(14).getClass()); - assertEquals(loader9, loaders.get(15)); - } - -} diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/TestUtil.java b/andhow-core/src/test/java/org/yarnandtail/andhow/TestUtil.java new file mode 100644 index 00000000..9b1ce7db --- /dev/null +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/TestUtil.java @@ -0,0 +1,92 @@ +package org.yarnandtail.andhow; + +import org.junit.platform.commons.function.Try; +import org.junit.platform.commons.support.HierarchyTraversalMode; +import org.junit.platform.commons.support.ReflectionSupport; +import org.junit.platform.commons.util.Preconditions; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import static org.junit.platform.commons.util.ReflectionUtils.HierarchyTraversalMode.BOTTOM_UP; + +/** + * Testing Utils + */ +public class TestUtil { + + /** + * Invokes the named String returning method on the instance via reflection, + * bypassing visibility. + * + * For some tests, this is an easy way test the internal effect of some actions. + * JUnit ReflectionSupport *should* find methods without having to pass types, but + * this bug prevents that: + * https://github.com/junit-team/junit5/issues/2663 + * + * @param instance The object instance to call the method on. + * @param name The name of a String returning method, which must be an instance method. + * @param args Arguments to the method + * @param types The argument types of the method which must match the method, not the args. + * @return The String returned by the method + * @throws Exception + */ + public static String stringMethod(Object instance, String name, Object[] args, Class[] types) { + return (String)invokeMethod(instance, name, args, types); + } + + public static String stringMethod(Object instance, String name, Object arg, Class type) { + return (String)invokeMethod(instance, name, new Object[]{arg}, new Class[]{type}); + } + + public static String stringMethod(Object instance, String name, Object... args) { + return (String)invokeMethod(instance, name, args, getTypes(args)); + } + + public static Object getField(Object instance, String name) { + List fields = ReflectionSupport.findFields(instance.getClass(), f -> f.getName().equals(name), HierarchyTraversalMode.BOTTOM_UP); + + if (fields.size() != 1) { + throw new IllegalArgumentException("Expected to find 1 field, instead found: " + fields.size()); + } + + Optional optObj = ReflectionSupport.tryToReadFieldValue(fields.get(0), instance).toOptional(); + + if (optObj.isPresent()) { + return optObj.get(); + } else { + throw new IllegalArgumentException("Unable to return a value"); + } + } + + public static Object invokeMethod(Object instance, String name, Object[] args, Class[] types) { + + Optional method = ReflectionSupport.findMethod(instance.getClass(), name, types); + + if (method.isPresent()) { + return ReflectionSupport.invokeMethod(method.get(), instance, args); + } else { + throw new IllegalArgumentException("Method not found"); + } + } + + public static Class[] getTypes(Object... args) { + List types = new ArrayList(); + + if (args != null) { + types = Arrays.stream(args).map(a -> a.getClass()).collect(Collectors.toList()); + } + + return types.toArray(new Class[types.size()]); + } + + + +} diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/api/AppFatalExceptionTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/api/AppFatalExceptionTest.java new file mode 100644 index 00000000..5d872469 --- /dev/null +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/api/AppFatalExceptionTest.java @@ -0,0 +1,81 @@ +package org.yarnandtail.andhow.api; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class AppFatalExceptionTest { + + @Test + public void testConstructorFromString() { + AppFatalException instance = new AppFatalException("test"); + assertEquals(instance.getMessage(), "test"); + assertNotNull(instance.getProblems()); + } + + @Test + public void testConstructorFromStringAndProblemList() { + ProblemList problems = new ProblemList(); + AppFatalException instance = new AppFatalException("test", problems); + assertEquals(instance.getMessage(), "test"); + assertNotNull(instance.getProblems()); + + problems = null; + instance = new AppFatalException("test", problems); + assertEquals(instance.getMessage(), "test"); + assertNotNull(instance.getProblems()); + } + + @Test + public void testConstructorFromStringAndProblem() { + Problem problem = new TestProblem(); + AppFatalException instance = new AppFatalException("test", problem); + assertEquals(instance.getMessage(), "test"); + assertNotNull(instance.getProblems()); + assertEquals(instance.getProblems().get(0).getFullMessage(), "TEST MESSAGE"); + + problem = null; + instance = new AppFatalException("test", problem); + assertEquals(instance.getMessage(), "test"); + assertNotNull(instance.getProblems()); + } + + @Test + public void testConstructorFromProblem() { + Problem problem = new TestProblem(); + AppFatalException instance = new AppFatalException(problem); + assertEquals(instance.getMessage(), problem.getFullMessage()); + assertNotNull(instance.getProblems()); + assertEquals(instance.getProblems().get(0), problem); + + problem = null; + instance = new AppFatalException(problem); + assertEquals(instance.getMessage(), "Unknown AndHow fatal exception"); + assertNotNull(instance.getProblems()); + assertEquals(0, instance.getProblems().size()); + } + + @Test + public void testSampleDirectory() { + AppFatalException instance = new AppFatalException("test"); + instance.setSampleDirectory("test/path"); + assertEquals(instance.getSampleDirectory(), "test/path"); + } + + class TestProblem implements Problem { + + @Override + public String getFullMessage() { + return "TEST MESSAGE"; + } + + @Override + public String getProblemContext() { + return "TEST PROBLEM CONTEXT"; + } + + @Override + public String getProblemDescription() { + return "TEST PROBLEM DESCRIPTION"; + } + } +} \ No newline at end of file diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/api/LoaderValuesTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/api/LoaderValuesTest.java index bf3be355..0fbf03c7 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/api/LoaderValuesTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/api/LoaderValuesTest.java @@ -1,10 +1,10 @@ package org.yarnandtail.andhow.api; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.load.*; import java.util.*; -import org.junit.*; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; import org.yarnandtail.andhow.property.StrProp; diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/api/NameTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/api/NameTest.java index 5ed13827..982d50c5 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/api/NameTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/api/NameTest.java @@ -1,7 +1,7 @@ package org.yarnandtail.andhow.api; -import static org.junit.Assert.*; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; /** * diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/api/PropertyTypeTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/api/PropertyTypeTest.java index 39f9233d..a7660a34 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/api/PropertyTypeTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/api/PropertyTypeTest.java @@ -1,9 +1,9 @@ package org.yarnandtail.andhow.api; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class PropertyTypeTest { diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/example/restclient/SampleRestClientAppTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/example/restclient/SampleRestClientAppTest.java deleted file mode 100644 index 445ae097..00000000 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/example/restclient/SampleRestClientAppTest.java +++ /dev/null @@ -1,138 +0,0 @@ -package org.yarnandtail.andhow.example.restclient; - -import java.util.ArrayList; - -import static org.junit.Assert.*; - -import org.junit.Before; -import org.junit.Test; -import org.springframework.mock.jndi.SimpleNamingContextBuilder; -import org.yarnandtail.andhow.*; -import org.yarnandtail.andhow.api.AppFatalException; -import org.yarnandtail.andhow.api.Property; -import org.yarnandtail.andhow.internal.LoaderProblem; -import org.yarnandtail.andhow.internal.ValueProblem; -import org.yarnandtail.andhow.load.KeyValuePairLoader; - -/** - * - * @author eeverman - */ -public class SampleRestClientAppTest extends AndHowCoreTestBase { - - String[] cmdLineArgs = new String[0]; - private static final String GROUP_PATH = "org.yarnandtail.andhow.example.restclient.SampleRestClientGroup"; - - - @Before - public void setup() { - - cmdLineArgs = new String[0]; - - } - - @Test - public void testAllValuesAreSet() { - - cmdLineArgs = new String[] { - GROUP_PATH + ".CLASSPATH_PROP_FILE" + KeyValuePairLoader.KVP_DELIMITER + - "/org/yarnandtail/andhow/example/restclient/all.points.speced.properties" - }; - - AndHowConfiguration config = AndHowCoreTestConfig.instance() - .group(SampleRestClientGroup.class) - .setCmdLineArgs(cmdLineArgs) - .setClasspathPropFilePath(SampleRestClientGroup.CLASSPATH_PROP_FILE) - .classpathPropertiesRequired(); - - AndHow.instance(config); - - assertEquals("/org/yarnandtail/andhow/example/restclient/all.points.speced.properties", - SampleRestClientGroup.CLASSPATH_PROP_FILE.getValue()); - assertEquals(" Big App ", SampleRestClientGroup.APP_NAME.getValue()); - assertEquals("aquarius.usgs.gov", SampleRestClientGroup.REST_HOST.getValue()); - assertEquals(new Integer(8080), SampleRestClientGroup.REST_PORT.getValue()); - assertEquals("doquery/", SampleRestClientGroup.REST_SERVICE_NAME.getValue()); - assertEquals("abc123", SampleRestClientGroup.AUTH_KEY.getValue()); - assertEquals(new Integer(4), SampleRestClientGroup.RETRY_COUNT.getValue()); - assertFalse(SampleRestClientGroup.REQUEST_META_DATA.getValue()); - assertTrue(SampleRestClientGroup.REQUEST_SUMMARY_DATA.getValue()); - - } - - @Test - public void testMinimumPropsAreSet() { - - cmdLineArgs = new String[] { - GROUP_PATH + ".CLASSPATH_PROP_FILE" + KeyValuePairLoader.KVP_DELIMITER + - "/org/yarnandtail/andhow/example/restclient/minimum.points.speced.properties" - }; - - AndHowConfiguration config = AndHowCoreTestConfig.instance() - .group(SampleRestClientGroup.class) - .setCmdLineArgs(cmdLineArgs) - .setClasspathPropFilePath(SampleRestClientGroup.CLASSPATH_PROP_FILE) - .classpathPropertiesRequired(); - - AndHow.instance(config); - - assertEquals("/org/yarnandtail/andhow/example/restclient/minimum.points.speced.properties", - SampleRestClientGroup.CLASSPATH_PROP_FILE.getValue()); - assertEquals("aquarius.usgs.gov", SampleRestClientGroup.REST_HOST.getValue()); - assertEquals(new Integer(8080), SampleRestClientGroup.REST_PORT.getValue()); - assertEquals("query/", SampleRestClientGroup.REST_SERVICE_NAME.getValue()); //a default value - assertEquals("abc123", SampleRestClientGroup.AUTH_KEY.getValue()); - assertEquals(new Integer(2), SampleRestClientGroup.RETRY_COUNT.getValue()); //a default - assertTrue(SampleRestClientGroup.REQUEST_META_DATA.getValue()); //a default - assertFalse(SampleRestClientGroup.REQUEST_SUMMARY_DATA.getValue()); //a default - - } - - @Test - public void testInvalidValues() throws Exception { - - SimpleNamingContextBuilder jndi = getJndi(); - jndi.activate(); - - cmdLineArgs = new String[] { - GROUP_PATH + ".CLASSPATH_PROP_FILE" + KeyValuePairLoader.KVP_DELIMITER + - "/org/yarnandtail/andhow/example/restclient/invalid.properties" - }; - - try { - - //Error expected b/c some values are invalid - AndHowConfiguration config = AndHowCoreTestConfig.instance() - .group(SampleRestClientGroup.class) - .setCmdLineArgs(cmdLineArgs) - .setClasspathPropFilePath(SampleRestClientGroup.CLASSPATH_PROP_FILE) - .classpathPropertiesRequired(); - - AndHow.instance(config); - - } catch (AppFatalException e) { - - //Value Problems (validation) - //Due to loading from a prop file, the order of the file is not preserved, - //so we cannot know the order that problems were encountered. - ArrayList> expectedProblemPoints = new ArrayList(); - expectedProblemPoints.add(SampleRestClientGroup.REST_HOST); - expectedProblemPoints.add(SampleRestClientGroup.REST_PORT); - expectedProblemPoints.add(SampleRestClientGroup.REST_SERVICE_NAME); - - assertEquals(3, e.getProblems().filter(ValueProblem.class).size()); - assertTrue(expectedProblemPoints.contains(e.getProblems().filter(ValueProblem.class).get(0).getBadValueCoord().getProperty())); - assertTrue(expectedProblemPoints.contains(e.getProblems().filter(ValueProblem.class).get(1).getBadValueCoord().getProperty())); - assertTrue(expectedProblemPoints.contains(e.getProblems().filter(ValueProblem.class).get(2).getBadValueCoord().getProperty())); - - // - // Loader problems - assertEquals(1, e.getProblems().filter(LoaderProblem.class).size()); - assertEquals(SampleRestClientGroup.RETRY_COUNT, e.getProblems().filter(LoaderProblem.class).get(0).getBadValueCoord().getProperty()); - } - - - } - - -} diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/example/restclient/SampleRestClientGroup.java b/andhow-core/src/test/java/org/yarnandtail/andhow/example/restclient/SampleRestClientGroup.java deleted file mode 100644 index 37a93297..00000000 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/example/restclient/SampleRestClientGroup.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.yarnandtail.andhow.example.restclient; - -import org.yarnandtail.andhow.property.FlagProp; -import org.yarnandtail.andhow.property.IntProp; -import org.yarnandtail.andhow.property.StrProp; - -/** - * - * @author eeverman - */ -public interface SampleRestClientGroup { - - StrProp CLASSPATH_PROP_FILE = StrProp.builder().desc("Classpath location of a properties file w/ props").build(); - StrProp APP_NAME = StrProp.builder().aliasIn("app.name").aliasIn("app_name").build(); - StrProp REST_HOST = StrProp.builder().mustMatchRegex(".*\\.usgs\\.gov") .mustBeNonNull().build(); - IntProp REST_PORT = IntProp.builder().mustBeNonNull().mustBeGreaterThanOrEqualTo(80).mustBeLessThan(10000).build(); - StrProp REST_SERVICE_NAME = StrProp.builder().defaultValue("query/").mustEndWith("/").build(); - StrProp AUTH_KEY = StrProp.builder().mustBeNonNull().build(); - IntProp RETRY_COUNT = IntProp.builder().defaultValue(2).build(); - FlagProp REQUEST_META_DATA = FlagProp.builder().defaultValue(true).build(); - FlagProp REQUEST_SUMMARY_DATA = FlagProp.builder().build(); -} diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/internal/ConstructionProblemTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/internal/ConstructionProblemTest.java new file mode 100644 index 00000000..401d5858 --- /dev/null +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/internal/ConstructionProblemTest.java @@ -0,0 +1,193 @@ +package org.yarnandtail.andhow.internal; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import org.junit.jupiter.api.Test; +import org.yarnandtail.andhow.api.*; +import org.yarnandtail.andhow.AndHowInit; +import java.util.ArrayList; +import java.util.List; +import org.yarnandtail.andhow.AndHow; + + + +public class ConstructionProblemTest { + + @Test + public void testNoUniqueNames() { + GroupProxy refGroup = mock(GroupProxy.class); + Class someUserClass = Object.class; + when(refGroup.getProxiedGroup()).thenReturn(someUserClass); + Property refProperty = mock(Property.class); + GroupProxy badGroup = mock(GroupProxy.class); + when(badGroup.getProxiedGroup()).thenReturn(someUserClass); + Property badProperty = mock(Property.class); + String conflictName = "conflict name test"; + ConstructionProblem.NonUniqueNames instance = new ConstructionProblem. + NonUniqueNames(refGroup, refProperty, badGroup, badProperty, conflictName); + + assertNotNull(instance.getProblemDescription()); + assertEquals(instance.getConflictName(), conflictName); + assertEquals(instance.getRefPropertyCoord().getGroup(), someUserClass); + assertEquals(instance.getBadPropertyCoord().getGroup(), someUserClass); + assertEquals(instance.getRefPropertyCoord().getProperty(), refProperty); + assertEquals(instance.getBadPropertyCoord().getProperty(), badProperty); + } + + @Test + public void testDuplicateProperty() { + Class someUserClass = Object.class; + GroupProxy refGroup = mock(GroupProxy.class); + when(refGroup.getProxiedGroup()).thenReturn(someUserClass); + Property refProperty = mock(Property.class); + GroupProxy badGroup = mock(GroupProxy.class); + when(badGroup.getProxiedGroup()).thenReturn(someUserClass); + Property badProperty = mock(Property.class); + ConstructionProblem.DuplicateProperty instance = new ConstructionProblem. + DuplicateProperty(refGroup, refProperty, badGroup, badProperty); + + assertNotNull(instance.getBadPropertyCoord()); + assertNotNull(instance.getRefPropertyCoord()); + assertNotNull(instance.getProblemDescription()); + assertEquals(instance.getRefPropertyCoord().getGroup(), someUserClass); + assertEquals(instance.getBadPropertyCoord().getGroup(), someUserClass); + assertEquals(instance.getRefPropertyCoord().getProperty(), refProperty); + assertEquals(instance.getBadPropertyCoord().getProperty(), badProperty); + } + + @Test + public void testDuplicateLoader() { + Loader loader = mock(Loader.class); + ConstructionProblem.DuplicateLoader instance = new ConstructionProblem.DuplicateLoader(loader); + + assertNotNull(instance.getLoader()); + assertNotNull(instance.getProblemContext()); + assertNotNull(instance.getProblemDescription()); + } + + @Test + public void testLoaderPropertyIsNull() { + Loader loader = mock(Loader.class); + ConstructionProblem.LoaderPropertyIsNull instance = new ConstructionProblem.LoaderPropertyIsNull(loader); + + assertNotNull(instance.getLoader()); + assertNotNull(instance.getProblemContext()); + assertNotNull(instance.getProblemContext()); + } + + @Test + public void testLoaderPropertyNotRegistered() { + Loader loader = mock(Loader.class); + Property property = mock(Property.class); + ConstructionProblem.LoaderPropertyNotRegistered instance = new ConstructionProblem. + LoaderPropertyNotRegistered(loader, property); + + assertNotNull(instance.getLoader()); + assertNotNull(instance.getProperty()); + assertNotNull(instance.getProblemDescription()); + assertEquals(instance.getProperty(), property); + } + + @Test + public void testSecurityException() { + String group = "test class"; + Exception exception = new Exception("test"); + ConstructionProblem.SecurityException instance = new ConstructionProblem. + SecurityException(exception, group.getClass()); + + assertNotNull(instance.getException()); + assertNotNull(instance.getProblemContext()); + assertNotNull(instance.getProblemDescription()); + } + + @Test + public void testPropertyNotPartOfGroup() { + GroupProxy group = mock(GroupProxy.class); + Class testClass = Object.class; + when(group.getProxiedGroup()).thenReturn(testClass); + Property prop = mock(Property.class); + + ConstructionProblem.PropertyNotPartOfGroup instance = new ConstructionProblem. + PropertyNotPartOfGroup(group, prop); + + assertNotNull(instance.getProblemDescription()); + assertEquals(instance.getBadPropertyCoord().getGroup(), testClass); + assertEquals(instance.getBadPropertyCoord().getProperty(), prop); + } + + @Test + public void testExportException() { + Exception exception = new Exception("test"); + GroupProxy group = mock(GroupProxy.class); + Class testClass = Object.class; + when(group.getProxiedGroup()).thenReturn(testClass); + String message = "test message"; + ConstructionProblem.ExportException instance = new ConstructionProblem. + ExportException(exception, group, message); + + assertEquals(instance.getException().getMessage(), "test"); + assertNotNull(instance.getProblemDescription()); + assertEquals(instance.getBadPropertyCoord().getGroup(), testClass); + } + + @Test + public void testInvalidDefaultValue() { + GroupProxy group = mock(GroupProxy.class); + Class testClass = Object.class; + when(group.getProxiedGroup()).thenReturn(testClass); + Property prop = mock(Property.class); + String invalidMessage = "test invalid message"; + ConstructionProblem.InvalidDefaultValue instance = new ConstructionProblem. + InvalidDefaultValue(group, prop, invalidMessage); + + assertEquals(instance.getInvalidMessage(), invalidMessage); + assertNotNull(instance.getProblemDescription()); + assertEquals(instance.getBadPropertyCoord().getGroup(), testClass); + assertEquals(instance.getBadPropertyCoord().getProperty(), prop); + } + + @Test + public void testInvalidValidationConfiguration() { + GroupProxy group = mock(GroupProxy.class); + Class testClass = Object.class; + when(group.getProxiedGroup()).thenReturn(testClass); + Property property = mock(Property.class); + Validator valid = mock(Validator.class); + + ConstructionProblem.InvalidValidationConfiguration instance = new ConstructionProblem. + InvalidValidationConfiguration(group, property, valid); + + assertEquals(instance.getValidator(), valid); + assertNotNull(instance.getProblemDescription()); + assertEquals(instance.getBadPropertyCoord().getGroup(), testClass); + assertEquals(instance.getBadPropertyCoord().getProperty(), property); + } + + @Test + public void testTooManyAndHowInitInstances() { + AndHowInit item = mock(AndHowInit.class); + List instances = new ArrayList(); + instances.add(item); + + ConstructionProblem.TooManyAndHowInitInstances instance = new ConstructionProblem. + TooManyAndHowInitInstances(instances); + + assertNotNull(instance.getInstanceNames()); + assertNotNull(instance.getProblemDescription()); + } + + @Test + public void testInitiationLoop() { + AndHow.Initialization originalInit = mock(AndHow.Initialization.class); + AndHow.Initialization secondInit = mock(AndHow.Initialization.class); + + ConstructionProblem.InitiationLoopException instance = new ConstructionProblem. + InitiationLoopException(originalInit, secondInit); + + assertNotNull(instance.getOriginalInit()); + assertNotNull(instance.getSecondInit()); + assertNotNull(instance.getProblemDescription()); + assertNotNull(instance.getFullMessage()); + } + +} \ No newline at end of file diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/internal/StaticPropertyConfigurationImmutableTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/internal/StaticPropertyConfigurationImmutableTest.java index 2a958486..f9ed5d75 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/internal/StaticPropertyConfigurationImmutableTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/internal/StaticPropertyConfigurationImmutableTest.java @@ -4,9 +4,9 @@ import org.yarnandtail.andhow.util.AndHowUtil; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.api.*; import org.yarnandtail.andhow.name.CaseInsensitiveNaming; import org.yarnandtail.andhow.property.StrProp; diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/internal/StaticPropertyConfigurationMutableTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/internal/StaticPropertyConfigurationMutableTest.java index cfbf1079..a202a71a 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/internal/StaticPropertyConfigurationMutableTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/internal/StaticPropertyConfigurationMutableTest.java @@ -3,9 +3,9 @@ import org.yarnandtail.andhow.util.AndHowUtil; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.api.NamingStrategy; import org.yarnandtail.andhow.api.ProblemList; import org.yarnandtail.andhow.name.CaseInsensitiveNaming; diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/internal/ValueMapWithContextMutableTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/internal/ValueMapWithContextMutableTest.java index 868948d6..871464b3 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/internal/ValueMapWithContextMutableTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/internal/ValueMapWithContextMutableTest.java @@ -3,9 +3,9 @@ import java.util.ArrayList; import java.util.List; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.SimpleParams; import org.yarnandtail.andhow.api.*; import org.yarnandtail.andhow.load.PropFileOnClasspathLoader; diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/load/BaseForLoaderTests.java b/andhow-core/src/test/java/org/yarnandtail/andhow/load/BaseForLoaderTests.java new file mode 100644 index 00000000..5ed35801 --- /dev/null +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/load/BaseForLoaderTests.java @@ -0,0 +1,60 @@ +package org.yarnandtail.andhow.load; + +import org.junit.jupiter.api.BeforeEach; +import org.yarnandtail.andhow.api.GroupProxy; +import org.yarnandtail.andhow.internal.StaticPropertyConfigurationMutable; +import org.yarnandtail.andhow.internal.ValidatedValuesWithContextMutable; +import org.yarnandtail.andhow.name.CaseInsensitiveNaming; +import org.yarnandtail.andhow.property.*; +import org.yarnandtail.andhow.util.AndHowUtil; + +/** + * This is intended to be a base class for tests that directly test a single loader. + * As such, tests cannot apply default values or detect missing required values since + * that happens only after all loaders have run and higher level logic is applied. + * Thus, none of the example parameters have defaults or required flags. + * + * @author eeverman + */ +public class BaseForLoaderTests { + StaticPropertyConfigurationMutable appDef; + ValidatedValuesWithContextMutable appValuesBuilder; + + @BeforeEach + public void init() throws Exception { + appValuesBuilder = new ValidatedValuesWithContextMutable(); + CaseInsensitiveNaming bns = new CaseInsensitiveNaming(); + appDef = new StaticPropertyConfigurationMutable(bns); + GroupProxy proxy = AndHowUtil.buildGroupProxy(SimpleParams.class); + + appDef.addProperty(proxy, SimpleParams.STR_BOB); + appDef.addProperty(proxy, SimpleParams.STR_NULL); + appDef.addProperty(proxy, SimpleParams.STR_ENDS_WITH_XXX); + + appDef.addProperty(proxy, SimpleParams.LNG_TIME); + appDef.addProperty(proxy, SimpleParams.INT_NUMBER); + appDef.addProperty(proxy, SimpleParams.DBL_NUMBER); + + appDef.addProperty(proxy, SimpleParams.FLAG_FALSE); + appDef.addProperty(proxy, SimpleParams.FLAG_TRUE); + appDef.addProperty(proxy, SimpleParams.FLAG_NULL); + + } + + public interface SimpleParams { + //Strings + StrProp STR_BOB = StrProp.builder().aliasIn("String_Bob").aliasInAndOut("Stringy.Bob").defaultValue("bob").build(); + StrProp STR_NULL = StrProp.builder().aliasInAndOut("String_Null").build(); + StrProp STR_ENDS_WITH_XXX = StrProp.builder().mustEndWith("XXX").build(); + + //Some Numbers + LngProp LNG_TIME = LngProp.builder().aliasIn("lngIn").build(); + IntProp INT_NUMBER = IntProp.builder().aliasIn("intIn").build(); + DblProp DBL_NUMBER = DblProp.builder().aliasIn("dblIn").build(); + + //Flags + FlagProp FLAG_FALSE = FlagProp.builder().build(); + FlagProp FLAG_TRUE = FlagProp.builder().build(); + FlagProp FLAG_NULL = FlagProp.builder().build(); + } +} diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/load/FixedValueLoaderTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/load/FixedValueLoaderTest.java new file mode 100644 index 00000000..afdd69ef --- /dev/null +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/load/FixedValueLoaderTest.java @@ -0,0 +1,161 @@ +package org.yarnandtail.andhow.load; + +import org.junit.jupiter.api.Test; +import org.yarnandtail.andhow.PropertyValue; +import org.yarnandtail.andhow.api.LoaderValues; +import org.yarnandtail.andhow.api.ParsingException; +import org.yarnandtail.andhow.api.Problem; +import org.yarnandtail.andhow.internal.LoaderProblem; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; +import static org.yarnandtail.andhow.load.BaseForLoaderTests.SimpleParams.*; + +public class FixedValueLoaderTest extends BaseForLoaderTests { + + @Test + public void happyPathViaPropertyValues() { + + List props = new ArrayList(); + props.add(new PropertyValue(STR_BOB, "test")); + props.add(new PropertyValue(STR_NULL, "not_null")); + props.add(new PropertyValue(STR_ENDS_WITH_XXX, "XXX")); + + //spaces on non-string values should be trimmed and not matter + props.add(new PropertyValue(LNG_TIME, "60")); //as string + props.add(new PropertyValue(INT_NUMBER, 30)); //As exact value type (int) + props.add(new PropertyValue(DBL_NUMBER, "123.456D")); //as string + props.add(new PropertyValue(FLAG_TRUE, " false ")); + props.add(new PropertyValue(FLAG_FALSE, " true ")); + props.add(new PropertyValue(FLAG_NULL, " true ")); + + + FixedValueLoader loader = new FixedValueLoader(); + loader.setPropertyValues(props); + + LoaderValues result = loader.load(appDef, appValuesBuilder); + + assertEquals(0, result.getProblems().size()); + assertEquals(0L, result.getValues().stream().filter(p -> p.hasProblems()).count()); + assertEquals("test", result.getExplicitValue(SimpleParams.STR_BOB)); + assertEquals("not_null", result.getExplicitValue(SimpleParams.STR_NULL)); + assertEquals("XXX", result.getExplicitValue(STR_ENDS_WITH_XXX)); + assertEquals(60L, result.getExplicitValue(LNG_TIME)); + assertEquals(30, result.getExplicitValue(INT_NUMBER)); + assertEquals(Boolean.FALSE, result.getExplicitValue(FLAG_TRUE)); + assertEquals(Boolean.TRUE, result.getExplicitValue(FLAG_FALSE)); + assertEquals(Boolean.TRUE, result.getExplicitValue(FLAG_NULL)); + } + + @Test + public void happyPathViaKeyObjectPairs() throws ParsingException { + + String basePath = SimpleParams.class.getCanonicalName() + "."; + + List kops = new ArrayList(); + kops.add(new KeyObjectPair(basePath + "STR_BOB", "test")); + kops.add(new KeyObjectPair(basePath + "STR_NULL", "not_null")); + kops.add(new KeyObjectPair(basePath + "STR_ENDS_WITH_XXX", "XXX")); + kops.add(new KeyObjectPair(basePath + "LNG_TIME", 60L)); //As exact value type (Long) + kops.add(new KeyObjectPair(basePath + "INT_NUMBER", "30")); //Supply string for conversion + kops.add(new KeyObjectPair(basePath + "DBL_NUMBER", 123.456D)); //As exact value type (Double) + kops.add(new KeyObjectPair(basePath + "FLAG_TRUE", false)); + kops.add(new KeyObjectPair(basePath + "FLAG_FALSE", true)); + kops.add(new KeyObjectPair(basePath + "FLAG_NULL", Boolean.TRUE)); + + + FixedValueLoader loader = new FixedValueLoader(); + loader.setKeyObjectPairValues(kops); + + LoaderValues result = loader.load(appDef, appValuesBuilder); + + assertEquals(0, result.getProblems().size()); + assertEquals(0L, result.getValues().stream().filter(p -> p.hasProblems()).count()); + assertEquals("test", result.getExplicitValue(STR_BOB)); + assertEquals("not_null", result.getExplicitValue(STR_NULL)); + assertEquals("XXX", result.getExplicitValue(STR_ENDS_WITH_XXX)); + assertEquals(60L, result.getExplicitValue(LNG_TIME)); + assertEquals(30, result.getExplicitValue(INT_NUMBER)); + assertEquals(Boolean.FALSE, result.getExplicitValue(FLAG_TRUE)); + assertEquals(Boolean.TRUE, result.getExplicitValue(FLAG_FALSE)); + assertEquals(Boolean.TRUE, result.getExplicitValue(FLAG_NULL)); + } + + @Test + public void duplicatePropertyValuesCauseProblems() { + List props = new ArrayList(); + props.add(new PropertyValue(STR_BOB, "test")); + props.add(new PropertyValue(STR_BOB, "ignored because its a duplicate property entry")); + + FixedValueLoader loader = new FixedValueLoader(); + loader.setPropertyValues(props); + + LoaderValues result = loader.load(appDef, appValuesBuilder); + + //Make sure we have a duplicate problem reported + assertEquals(1, result.getProblems().size()); + assertEquals(0L, result.getValues().stream().filter(p -> p.hasProblems()).count()); //No problems at an ind level + Problem p = result.getProblems().get(0); + assertTrue(p instanceof LoaderProblem.DuplicatePropertyLoaderProblem); + assertEquals(STR_BOB, ((LoaderProblem.DuplicatePropertyLoaderProblem) p).getBadValueCoord().getProperty()); + + //The initial assignment should have still worked + assertEquals("test", result.getExplicitValue(SimpleParams.STR_BOB)); + } + + @Test + public void duplicateKeyObjectPairsCauseProblems() throws ParsingException { + String basePath = SimpleParams.class.getCanonicalName() + "."; + + List kops = new ArrayList(); + kops.add(new KeyObjectPair(basePath + "STR_BOB", "test")); + kops.add(new KeyObjectPair(basePath + "STR_BOB", "ignored because its a duplicate property entry")); + + + FixedValueLoader loader = new FixedValueLoader(); + loader.setKeyObjectPairValues(kops); + + LoaderValues result = loader.load(appDef, appValuesBuilder); + + //Make sure we have a duplicate problem reported + assertEquals(1, result.getProblems().size()); + assertEquals(0L, result.getValues().stream().filter(p -> p.hasProblems()).count()); //No problems at an ind level + Problem p = result.getProblems().get(0); + assertTrue(p instanceof LoaderProblem.DuplicatePropertyLoaderProblem); + assertEquals(STR_BOB, ((LoaderProblem.DuplicatePropertyLoaderProblem) p).getBadValueCoord().getProperty()); + + //The initial assignment should have still worked + assertEquals("test", result.getExplicitValue(SimpleParams.STR_BOB)); + } + + @Test + public void duplicateBetweenPVsAndKopsCauseProblems() throws ParsingException { + + //Set based on PropertyValues + List props = new ArrayList(); + props.add(new PropertyValue(STR_BOB, "test")); + + //Set based on KeyObjectPair + String basePath = SimpleParams.class.getCanonicalName() + "."; + List kops = new ArrayList(); + kops.add(new KeyObjectPair(basePath + "STR_BOB", "ignored because its a duplicate property entry")); + + FixedValueLoader loader = new FixedValueLoader(); + loader.setPropertyValues(props); + loader.setKeyObjectPairValues(kops); + + LoaderValues result = loader.load(appDef, appValuesBuilder); + + //Make sure we have a duplicate problem reported + assertEquals(1, result.getProblems().size()); + assertEquals(0L, result.getValues().stream().filter(p -> p.hasProblems()).count()); //No problems at an ind level + Problem p = result.getProblems().get(0); + assertTrue(p instanceof LoaderProblem.DuplicatePropertyLoaderProblem); + assertEquals(STR_BOB, ((LoaderProblem.DuplicatePropertyLoaderProblem) p).getBadValueCoord().getProperty()); + + //The initial assignment should have still worked + assertEquals("test", result.getExplicitValue(SimpleParams.STR_BOB)); + } +} diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/load/KVPTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/load/KVPTest.java index 9ddb2542..c128479c 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/load/KVPTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/load/KVPTest.java @@ -1,7 +1,7 @@ package org.yarnandtail.andhow.load; -import static org.junit.Assert.*; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.api.ParsingException; /** @@ -98,34 +98,46 @@ public void splitKVPGoodNameAndValuesWithMultiDelims() throws Exception { } - @Test(expected=ParsingException.class) - public void splitKVPBadEmptyFlagName() throws Exception { - KVP.splitKVP("=value", KeyValuePairLoader.KVP_DELIMITER); + @Test + public void splitKVPBadEmptyFlagName() { + assertThrows(ParsingException.class, () -> + KVP.splitKVP("=value", KeyValuePairLoader.KVP_DELIMITER) + ); } - @Test(expected=ParsingException.class) - public void splitKVPBadSpaceOnlyFlagName() throws Exception { - KVP.splitKVP(" =value", KeyValuePairLoader.KVP_DELIMITER); + @Test + public void splitKVPBadSpaceOnlyFlagName() { + assertThrows(ParsingException.class, () -> + KVP.splitKVP(" =value", KeyValuePairLoader.KVP_DELIMITER) + ); } - @Test(expected=ParsingException.class) - public void splitKVPBadAllSpaceAndTabFlagName() throws Exception { - KVP.splitKVP(" \t =value", KeyValuePairLoader.KVP_DELIMITER); + @Test + public void splitKVPBadAllSpaceAndTabFlagName() { + assertThrows(ParsingException.class, () -> + KVP.splitKVP(" \t =value", KeyValuePairLoader.KVP_DELIMITER) + ); } - @Test(expected=ParsingException.class) - public void splitKVPBadAllWhitespaceAndNewLinesFlagName() throws Exception { - KVP.splitKVP(" \t\n\r\f = value", KeyValuePairLoader.KVP_DELIMITER); + @Test + public void splitKVPBadAllWhitespaceAndNewLinesFlagName() { + assertThrows(ParsingException.class, () -> + KVP.splitKVP(" \t\n\r\f = value", KeyValuePairLoader.KVP_DELIMITER) + ); } - @Test(expected=ParsingException.class) - public void splitKVPBadAllBackspaceFlagName() throws Exception { - KVP.splitKVP("\b =value", KeyValuePairLoader.KVP_DELIMITER); + @Test + public void splitKVPBadAllBackspaceFlagName() { + assertThrows(ParsingException.class, () -> + KVP.splitKVP("\b =value", KeyValuePairLoader.KVP_DELIMITER) + ); } - @Test(expected=ParsingException.class) - public void splitKVPBadAllWhitespaceAndBackspaceFlagName() throws Exception { - KVP.splitKVP(" \b =value", KeyValuePairLoader.KVP_DELIMITER); + @Test + public void splitKVPBadAllWhitespaceAndBackspaceFlagName() { + assertThrows(ParsingException.class, () -> + KVP.splitKVP(" \b =value", KeyValuePairLoader.KVP_DELIMITER) + ); } @Test @@ -145,9 +157,11 @@ public void newKVPByNameOnlyGoodName() throws Exception { assertNull(kvp.getValue()); } - @Test(expected=ParsingException.class) - public void newKVPByNameOnlyBadEmptyName() throws Exception { - new KVP(" \t \r\n "); + @Test + public void newKVPByNameOnlyBadEmptyName() { + assertThrows(ParsingException.class, () -> + new KVP(" \t \r\n ") + ); } @Test @@ -163,8 +177,10 @@ public void newKVPByNameAndValueGood() throws Exception { assertNull(kvp.getValue()); } - @Test(expected=ParsingException.class) - public void newKVPByNameAndValueBadEmptyName() throws Exception { - new KVP(" \t \r\n ", "value"); + @Test + public void newKVPByNameAndValueBadEmptyName(){ + assertThrows(ParsingException.class, () -> + new KVP(" \t \r\n ", "value") + ); } } diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/load/KeyObjectPairTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/load/KeyObjectPairTest.java new file mode 100644 index 00000000..261697a0 --- /dev/null +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/load/KeyObjectPairTest.java @@ -0,0 +1,56 @@ +package org.yarnandtail.andhow.load; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.yarnandtail.andhow.api.ParsingException; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class KeyObjectPairTest { + + @Test + public void simpleTest() throws ParsingException { + KeyObjectPair kop1 = new KeyObjectPair(" Name1 ", " Value1 "); //two spaces pad start & end of name & value + KeyObjectPair kop2 = new KeyObjectPair("Name2", ""); + KeyObjectPair kop3 = new KeyObjectPair("Name3", 23L); + KeyObjectPair kop4 = new KeyObjectPair(" Name4 "); + KeyObjectPair kop5 = new KeyObjectPair("Name5", null); + + assertEquals("Name1", kop1.getName()); + assertEquals(" Value1 ", kop1.getValue()); + assertEquals("Name1 : \" Value1 \"", kop1.toString()); + + assertEquals("Name2", kop2.getName()); + assertEquals("", kop2.getValue()); + assertEquals("Name2 : \"\"", kop2.toString()); + + assertEquals("Name3", kop3.getName()); + assertEquals(23L, kop3.getValue()); + assertEquals("Name3 : \"23\"", kop3.toString()); + + assertEquals("Name4", kop4.getName()); + assertNull(kop4.getValue()); + assertEquals("Name4 : \"[null]\"", kop4.toString()); + + assertEquals("Name5", kop5.getName()); + assertNull(kop5.getValue()); + assertEquals("Name5 : \"[null]\"", kop5.toString()); + } + + @Test + public void emptyNameShouldBeAnError() { + assertThrows(ParsingException.class, () -> { + new KeyObjectPair(" ", "SomeValue"); + }); + + assertThrows(ParsingException.class, () -> { + new KeyObjectPair("", "SomeValue"); + }); + + assertThrows(ParsingException.class, () -> { + new KeyObjectPair(null, "SomeValue"); + }); + } + +} \ No newline at end of file diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/load/KeyValuePairLoaderTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/load/KeyValuePairLoaderTest.java index d54e2c62..44e675c5 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/load/KeyValuePairLoaderTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/load/KeyValuePairLoaderTest.java @@ -3,10 +3,10 @@ import java.util.ArrayList; import java.util.List; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.api.*; import org.yarnandtail.andhow.internal.*; import org.yarnandtail.andhow.name.CaseInsensitiveNaming; @@ -17,46 +17,12 @@ /** * Note: This directly tests a single loader so it is not possible to * test for missing required values. Loaders can't know if a value is missing - - * that only can be figured out after all loaders are comlete. + * that only can be figured out after all loaders are complete. * * @author eeverman */ -public class KeyValuePairLoaderTest { - - StaticPropertyConfigurationMutable appDef; - ValidatedValuesWithContextMutable appValuesBuilder; - - public interface SimpleParams { - //Strings - StrProp STR_BOB = StrProp.builder().aliasIn("String_Bob").aliasInAndOut("Stringy.Bob").defaultValue("bob").build(); - StrProp STR_NULL = StrProp.builder().aliasInAndOut("String_Null").build(); - StrProp STR_ENDS_WITH_XXX = StrProp.builder().mustEndWith("XXX").build(); - +public class KeyValuePairLoaderTest extends BaseForLoaderTests { - //Flags - FlagProp FLAG_FALSE = FlagProp.builder().defaultValue(false).build(); - FlagProp FLAG_TRUE = FlagProp.builder().defaultValue(true).build(); - FlagProp FLAG_NULL = FlagProp.builder().build(); - } - - @Before - public void init() throws Exception { - appValuesBuilder = new ValidatedValuesWithContextMutable(); - - CaseInsensitiveNaming bns = new CaseInsensitiveNaming(); - - GroupProxy proxy = AndHowUtil.buildGroupProxy(SimpleParams.class); - - appDef = new StaticPropertyConfigurationMutable(bns); - appDef.addProperty(proxy, SimpleParams.STR_BOB); - appDef.addProperty(proxy, SimpleParams.STR_NULL); - appDef.addProperty(proxy, SimpleParams.STR_ENDS_WITH_XXX); - appDef.addProperty(proxy, SimpleParams.FLAG_FALSE); - appDef.addProperty(proxy, SimpleParams.FLAG_TRUE); - appDef.addProperty(proxy, SimpleParams.FLAG_NULL); - - } - @Test public void testCmdLineLoaderHappyPathAsList() { diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/load/LoaderExceptionTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/load/LoaderExceptionTest.java index bea247c9..28b2bcd0 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/load/LoaderExceptionTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/load/LoaderExceptionTest.java @@ -4,7 +4,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import org.junit.Test; +import org.junit.jupiter.api.Test; public class LoaderExceptionTest { diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/load/PropFileOnClasspathLoaderAppTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/load/PropFileOnClasspathLoaderAppTest.java index 0376bdce..7c888cef 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/load/PropFileOnClasspathLoaderAppTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/load/PropFileOnClasspathLoaderAppTest.java @@ -2,9 +2,9 @@ import java.util.List; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.*; import org.yarnandtail.andhow.api.AppFatalException; import org.yarnandtail.andhow.api.Problem; diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/load/PropFileOnClasspathLoaderUnitTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/load/PropFileOnClasspathLoaderUnitTest.java index f0b3618b..37aff564 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/load/PropFileOnClasspathLoaderUnitTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/load/PropFileOnClasspathLoaderUnitTest.java @@ -2,10 +2,10 @@ import java.util.*; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.api.*; import org.yarnandtail.andhow.internal.StaticPropertyConfigurationMutable; import org.yarnandtail.andhow.internal.LoaderProblem; @@ -41,7 +41,7 @@ public interface SimpleParams { FlagProp FLAG_NULL = FlagProp.builder().build(); } - @Before + @BeforeEach public void init() throws Exception { appValuesBuilder = new ValidatedValuesWithContextMutable(); diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/load/PropFileOnFilesystemLoaderAppTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/load/PropFileOnFilesystemLoaderAppTest.java index e50141a5..2269b55f 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/load/PropFileOnFilesystemLoaderAppTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/load/PropFileOnFilesystemLoaderAppTest.java @@ -4,12 +4,12 @@ import java.net.URL; import java.util.List; import org.apache.commons.io.FileUtils; -import org.junit.After; +import org.junit.jupiter.api.AfterEach; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.*; import org.yarnandtail.andhow.api.AppFatalException; import org.yarnandtail.andhow.internal.ConstructionProblem.LoaderPropertyNotRegistered; @@ -31,7 +31,7 @@ public static interface TestProps { } - @Before + @BeforeEach public void init() throws Exception { //copy a properties file to a temp location @@ -42,7 +42,7 @@ public void init() throws Exception { } - @After + @AfterEach public void afterTest() { if (tempPropertiesFile != null) { tempPropertiesFile.delete(); diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/load/PropFileOnFilesystemLoaderUnitTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/load/PropFileOnFilesystemLoaderUnitTest.java index 48b73f80..4e7d23b2 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/load/PropFileOnFilesystemLoaderUnitTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/load/PropFileOnFilesystemLoaderUnitTest.java @@ -4,12 +4,12 @@ import java.net.URL; import java.util.ArrayList; import org.apache.commons.io.FileUtils; -import org.junit.After; +import org.junit.jupiter.api.AfterEach; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.api.*; import org.yarnandtail.andhow.internal.StaticPropertyConfigurationMutable; import org.yarnandtail.andhow.internal.LoaderProblem; @@ -46,7 +46,7 @@ public interface SimpleParams { FlagProp FLAG_NULL = FlagProp.builder().build(); } - @Before + @BeforeEach public void init() throws Exception { appValuesBuilder = new ValidatedValuesWithContextMutable(); @@ -73,7 +73,7 @@ public void init() throws Exception { } - @After + @AfterEach public void afterTest() { if (tempPropertiesFile != null) { tempPropertiesFile.delete(); diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/load/std/StdEnvVarLoaderTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/load/std/StdEnvVarLoaderTest.java index 4efd694d..a16bc2e2 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/load/std/StdEnvVarLoaderTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/load/std/StdEnvVarLoaderTest.java @@ -1,14 +1,11 @@ package org.yarnandtail.andhow.load.std; -import org.yarnandtail.andhow.load.std.StdEnvVarLoader; -import java.util.Collections; import java.util.HashMap; -import java.util.Map; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.api.*; import org.yarnandtail.andhow.util.AndHowUtil; import org.yarnandtail.andhow.internal.StaticPropertyConfigurationMutable; @@ -39,7 +36,7 @@ public interface SimpleParams { FlagProp FLAG_NULL = FlagProp.builder().build(); } - @Before + @BeforeEach public void init() throws Exception { appValuesBuilder = new ValidatedValuesWithContextMutable(); diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/load/std/StdJndiLoaderTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/load/std/StdJndiLoaderTest.java index aa1d0a13..0c0c47e0 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/load/std/StdJndiLoaderTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/load/std/StdJndiLoaderTest.java @@ -4,9 +4,9 @@ import java.time.LocalDateTime; import java.util.List; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.springframework.mock.jndi.SimpleNamingContextBuilder; import org.yarnandtail.andhow.*; import org.yarnandtail.andhow.api.*; diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/load/std/StdSysPropLoaderTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/load/std/StdSysPropLoaderTest.java index 769c1c2b..b770b812 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/load/std/StdSysPropLoaderTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/load/std/StdSysPropLoaderTest.java @@ -1,12 +1,12 @@ package org.yarnandtail.andhow.load.std; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.yarnandtail.andhow.util.AndHowUtil; -import org.junit.After; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.api.*; import org.yarnandtail.andhow.internal.*; import org.yarnandtail.andhow.name.CaseInsensitiveNaming; @@ -35,7 +35,7 @@ public interface SimpleParams { FlagProp FLAG_NULL = FlagProp.builder().build(); } - @Before + @BeforeEach public void init() throws Exception { appValuesBuilder = new ValidatedValuesWithContextMutable(); @@ -55,7 +55,7 @@ public void init() throws Exception { } - @After + @AfterEach public void post() throws Exception { clearSysProps(); } diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/name/CaseInsensitiveNamingTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/name/CaseInsensitiveNamingTest.java index e3e64305..564bbf7a 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/name/CaseInsensitiveNamingTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/name/CaseInsensitiveNamingTest.java @@ -1,8 +1,8 @@ package org.yarnandtail.andhow.name; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.api.PropertyNaming; import org.yarnandtail.andhow.property.StrProp; import org.yarnandtail.andhow.api.GroupProxy; diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/property/BigDecPropTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/property/BigDecPropTest.java new file mode 100644 index 00000000..06221501 --- /dev/null +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/property/BigDecPropTest.java @@ -0,0 +1,115 @@ +package org.yarnandtail.andhow.property; + +import org.junit.jupiter.api.Test; +import org.springframework.mock.jndi.SimpleNamingContextBuilder; +import org.yarnandtail.andhow.api.AppFatalException; +import org.yarnandtail.andhow.api.Problem; +import org.yarnandtail.andhow.api.ProblemList; +import org.yarnandtail.andhow.internal.RequirementProblem.NonNullPropertyProblem; +import org.yarnandtail.andhow.internal.ValueProblem.InvalidValueProblem; + +import javax.naming.NamingException; + +import static org.junit.jupiter.api.Assertions.*; + +import java.math.BigDecimal; + +/** + * Tests BigDecProp instances as they would be used in an app. + * + * Focuses on builder functionality, validation, and metadata. + * + * @author chace86 + */ +public class BigDecPropTest extends PropertyTestBase { + private static final BigDecimal VALUE_1 = new BigDecimal("10.789"); + private static final BigDecimal GREATER_THAN_VALUE = new BigDecimal("101.123E+5"); + private static final BigDecimal GREATER_THAN_OR_EQUAL_VALUE = new BigDecimal("100.123456"); + private static final BigDecimal LESS_THAN_VALUE = GREATER_THAN_VALUE.negate(); + private static final BigDecimal LESS_THAN_VALUE_OR_EQUAL_VALUE = GREATER_THAN_OR_EQUAL_VALUE; + private static final BigDecimal DEFAULT_VALUE = new BigDecimal("456.456"); + private static final BigDecimal JNDI_VALUE = new BigDecimal("777.777"); + private static final BigDecimal SYS_PROP_VALUE = new BigDecimal("-523.789"); + private static final String DESCRIPTION = "BigDecimal description"; + private static final String GREATER_THAN_JNDI_PATH = "org.yarnandtail.andhow.property.BigDecPropTest.BigDecGroup.GREATER_THAN"; + private static final String LESS_THAN_SYS_PROP_PATH = "org.yarnandtail.andhow.property.BigDecPropTest.BigDecGroup.LESS_THAN"; + + @Test + public void happyPathTest() { + this.buildConfig(this, "_happyPath", BigDecGroup.class); + assertEquals(VALUE_1, BigDecGroup.NOT_NULL.getValue()); + assertEquals(DESCRIPTION, BigDecGroup.NOT_NULL.getDescription()); + assertEquals(GREATER_THAN_VALUE, BigDecGroup.GREATER_THAN.getValue()); + assertEquals(GREATER_THAN_OR_EQUAL_VALUE, BigDecGroup.GREATER_THAN_OR_EQUAL.getValue()); + assertEquals(LESS_THAN_VALUE, BigDecGroup.LESS_THAN.getValue()); + assertEquals(LESS_THAN_VALUE_OR_EQUAL_VALUE, BigDecGroup.LESS_THAN_OR_EQUAL.getValue()); + assertEquals(new BigDecimal("789.789"), BigDecGroup.DEFAULT.getValue()); + assertNull(BigDecGroup.NULL.getValue()); + } + + @Test + public void happyPathTest_DefaultValue() { + this.buildConfig(this, "_default", BigDecGroup.class); + assertEquals(DEFAULT_VALUE, BigDecGroup.DEFAULT.getDefaultValue()); + } + + @Test + public void happyPathTest_Alias() { + this.buildConfig(this, "_default", BigDecGroup.class); + assertEquals(GREATER_THAN_VALUE, BigDecGroup.GREATER_THAN.getValue()); + assertEquals("alias", BigDecGroup.GREATER_THAN.getRequestedAliases().get(0).getActualName()); + } + + @Test + public void happyPathTest_JndiProp() throws NamingException { + SimpleNamingContextBuilder jndi = getJndi(); + jndi.bind("java:" + GREATER_THAN_JNDI_PATH, JNDI_VALUE); + jndi.activate(); + this.buildConfig(this, "_happyPath", BigDecGroup.class); + assertEquals(JNDI_VALUE, BigDecGroup.GREATER_THAN.getValue()); + } + + @Test + public void happyPathTest_SystemProp() { + System.setProperty(LESS_THAN_SYS_PROP_PATH, SYS_PROP_VALUE.toString()); + this.buildConfig(this, "_happyPath", BigDecGroup.class); + assertEquals(SYS_PROP_VALUE, BigDecGroup.LESS_THAN.getValue()); + } + + @Test + public void badPathTest_InvalidValues() { + try { + this.buildConfig(this, "_badPath", BigDecGroup.class); + fail("Should throw exception!"); + } catch(AppFatalException e) { + ProblemList problems = e.getProblems(); + for (Problem problem : problems) { + assertTrue(problem instanceof InvalidValueProblem); + } + assertEquals(4, problems.size()); + } + } + + @Test + public void badPathTest_NullProperty() { + try { + this.buildConfig(this, "_missingProps", BigDecGroup.class); + fail("Should throw exception!"); + } catch (AppFatalException e) { + ProblemList problems = e.getProblems(); + assertEquals(1, problems.size()); + Problem problem = problems.get(0); + assertTrue(problem instanceof NonNullPropertyProblem); + } + } + + public interface BigDecGroup { + BigDecProp NOT_NULL = BigDecProp.builder().mustBeNonNull().desc(DESCRIPTION).build(); + BigDecProp NULL = BigDecProp.builder().build(); + BigDecProp GREATER_THAN = BigDecProp.builder().mustBeGreaterThan(GREATER_THAN_OR_EQUAL_VALUE).aliasInAndOut("alias").build(); + BigDecProp GREATER_THAN_OR_EQUAL = BigDecProp.builder().mustBeGreaterThanOrEqualTo(GREATER_THAN_OR_EQUAL_VALUE).build(); + BigDecProp LESS_THAN = BigDecProp.builder().mustBeLessThan(GREATER_THAN_OR_EQUAL_VALUE).build(); + BigDecProp LESS_THAN_OR_EQUAL = BigDecProp.builder().mustBeLessThanOrEqualTo(LESS_THAN_VALUE_OR_EQUAL_VALUE).build(); + BigDecProp DEFAULT = BigDecProp.builder().defaultValue(DEFAULT_VALUE).build(); + } +} diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/property/BolPropTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/property/BolPropTest.java index 1d3b5e25..ac7251ba 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/property/BolPropTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/property/BolPropTest.java @@ -1,8 +1,8 @@ package org.yarnandtail.andhow.property; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; import org.yarnandtail.andhow.api.*; import org.yarnandtail.andhow.internal.RequirementProblem; diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/property/PropertyBuilderBaseTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/property/PropertyBuilderBaseTest.java index 4260b94b..9d93bfc7 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/property/PropertyBuilderBaseTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/property/PropertyBuilderBaseTest.java @@ -1,7 +1,7 @@ package org.yarnandtail.andhow.property; -import static org.junit.Assert.*; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.api.AppFatalException; import org.yarnandtail.andhow.api.Name; import org.yarnandtail.andhow.valuetype.FlagType; @@ -52,32 +52,40 @@ public void testAliasesHappyPath() { assertTrue(kathy.isIn() && kathy.isOut()); } - @Test(expected=AppFatalException.class) + @Test public void testAliasesSlashCharactersNotAllowed() { TestBuilder builder = new TestBuilder(); - - builder.aliasInAndOut("a/path/looking/one"); + + assertThrows(AppFatalException.class, () -> + builder.aliasInAndOut("a/path/looking/one") + ); } - @Test(expected=AppFatalException.class) + @Test public void testAliasesQuoteCharactersNotAllowed() { TestBuilder builder = new TestBuilder(); - - builder.aliasInAndOut("\"NoQuotes\""); + + assertThrows(AppFatalException.class, () -> + builder.aliasInAndOut("\"NoQuotes\"") + ); } - @Test(expected=AppFatalException.class) + @Test public void testAliasesSpaceCharactersNotAllowed() { TestBuilder builder = new TestBuilder(); - - builder.aliasInAndOut(" NoSpaces"); + + assertThrows(AppFatalException.class, () -> + builder.aliasInAndOut(" NoSpaces") + ); } - @Test(expected=AppFatalException.class) + @Test public void testAliasesNullNotAllowed() { TestBuilder builder = new TestBuilder(); - - builder.aliasInAndOut(null); + + assertThrows(AppFatalException.class, () -> + builder.aliasInAndOut(null) + ); } } diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/property/QuotedSpacePreservingTrimmerTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/property/QuotedSpacePreservingTrimmerTest.java index fbef90f3..c9cbc342 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/property/QuotedSpacePreservingTrimmerTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/property/QuotedSpacePreservingTrimmerTest.java @@ -2,8 +2,8 @@ */ package org.yarnandtail.andhow.property; -import static org.junit.Assert.*; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; /** * diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/property/StrPropTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/property/StrPropTest.java index 30d95c71..ceacf2d6 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/property/StrPropTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/property/StrPropTest.java @@ -1,8 +1,8 @@ package org.yarnandtail.andhow.property; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; import org.yarnandtail.andhow.api.*; import org.yarnandtail.andhow.internal.ValueProblem; diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/property/TrimToNullTrimmerTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/property/TrimToNullTrimmerTest.java index b79b1614..c3938d0a 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/property/TrimToNullTrimmerTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/property/TrimToNullTrimmerTest.java @@ -1,8 +1,8 @@ package org.yarnandtail.andhow.property; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Tests TrimToNullTrimmer instances as they would be used in an app. diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/sample/JndiLoaderSamplePrinterTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/sample/JndiLoaderSamplePrinterTest.java index 83bbc5ce..bdeb8311 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/sample/JndiLoaderSamplePrinterTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/sample/JndiLoaderSamplePrinterTest.java @@ -3,8 +3,9 @@ package org.yarnandtail.andhow.sample; import java.io.UnsupportedEncodingException; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; import org.yarnandtail.andhow.api.GroupProxyMutable; import org.yarnandtail.andhow.internal.NameAndProperty; import org.yarnandtail.andhow.internal.StaticPropertyConfigurationMutable; @@ -13,8 +14,6 @@ import org.yarnandtail.andhow.property.StrProp; import org.yarnandtail.andhow.util.TextUtil; -import static org.junit.Assert.*; - /** * * @author ericeverman @@ -31,7 +30,7 @@ public static interface Config { .mustBeNonNull().aliasIn("mp2").aliasInAndOut("mp2_alias2").aliasOut("mp2_out").build(); } - @Before + @BeforeEach public void setup() { config = new StaticPropertyConfigurationMutable(new CaseInsensitiveNaming()); @@ -42,8 +41,8 @@ public void setup() { groupProxy1.addProperty(new NameAndProperty("MY_PROP1", Config.MY_PROP1)); groupProxy1.addProperty(new NameAndProperty("MY_PROP2", Config.MY_PROP2)); - assertNull("Error adding property", config.addProperty(groupProxy1, Config.MY_PROP1)); - assertNull("Error adding property", config.addProperty(groupProxy1, Config.MY_PROP2)); + assertNull(config.addProperty(groupProxy1, Config.MY_PROP1)); + assertNull(config.addProperty(groupProxy1, Config.MY_PROP2)); } diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/sample/PropFileLoaderSamplePrinterTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/sample/PropFileLoaderSamplePrinterTest.java index 75a4ded4..0e8f4e54 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/sample/PropFileLoaderSamplePrinterTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/sample/PropFileLoaderSamplePrinterTest.java @@ -3,8 +3,8 @@ package org.yarnandtail.andhow.sample; import java.io.UnsupportedEncodingException; -import org.junit.Before; -import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.api.GroupProxyMutable; import org.yarnandtail.andhow.internal.NameAndProperty; import org.yarnandtail.andhow.internal.StaticPropertyConfigurationMutable; @@ -13,7 +13,7 @@ import org.yarnandtail.andhow.property.StrProp; import org.yarnandtail.andhow.util.TextUtil; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * @@ -31,7 +31,7 @@ public static interface Config { .mustBeNonNull().aliasIn("mp2").aliasInAndOut("mp2_alias2").aliasOut("mp2_out").build(); } - @Before + @BeforeEach public void setup() { config = new StaticPropertyConfigurationMutable(new CaseInsensitiveNaming()); @@ -42,8 +42,8 @@ public void setup() { groupProxy1.addProperty(new NameAndProperty("MY_PROP1", Config.MY_PROP1)); groupProxy1.addProperty(new NameAndProperty("MY_PROP2", Config.MY_PROP2)); - assertNull("Error adding property", config.addProperty(groupProxy1, Config.MY_PROP1)); - assertNull("Error adding property", config.addProperty(groupProxy1, Config.MY_PROP2)); + assertNull(config.addProperty(groupProxy1, Config.MY_PROP1)); + assertNull(config.addProperty(groupProxy1, Config.MY_PROP2)); } diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/sample/TextLineTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/sample/TextLineTest.java index cb8dece2..e34826a7 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/sample/TextLineTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/sample/TextLineTest.java @@ -2,10 +2,10 @@ */ package org.yarnandtail.andhow.sample; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.util.TextUtil; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/service/PropertyRegistrarLoaderTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/service/PropertyRegistrarLoaderTest.java index 78cf0277..67d9fd08 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/service/PropertyRegistrarLoaderTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/service/PropertyRegistrarLoaderTest.java @@ -2,12 +2,12 @@ import java.util.ArrayList; import java.util.List; -import org.yarnandtail.andhow.service.PropertyRegistrationList; -import org.junit.Test; + +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.api.GroupProxy; import org.yarnandtail.andhow.property.*; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/service/PropertyRegistrationListTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/service/PropertyRegistrationListTest.java index 01cf495b..2eb18d44 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/service/PropertyRegistrationListTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/service/PropertyRegistrationListTest.java @@ -1,9 +1,8 @@ package org.yarnandtail.andhow.service; -import org.yarnandtail.andhow.service.PropertyRegistrationList; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/service/PropertyRegistrationTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/service/PropertyRegistrationTest.java index b29b3381..3437c709 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/service/PropertyRegistrationTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/service/PropertyRegistrationTest.java @@ -2,11 +2,10 @@ */ package org.yarnandtail.andhow.service; -import org.yarnandtail.andhow.service.PropertyRegistration; import java.util.List; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/util/AndHowLogTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/util/AndHowLogTest.java index 88de69aa..99c0402f 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/util/AndHowLogTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/util/AndHowLogTest.java @@ -5,9 +5,12 @@ import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.logging.Level; -import org.junit.*; -import static org.junit.Assert.*; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; /** * @@ -25,7 +28,7 @@ public class AndHowLogTest { public AndHowLogTest() { } - @Before + @BeforeEach public void setUp() { AndHowLogHandler handler = (AndHowLogHandler) log.getHandlers()[0]; @@ -41,7 +44,7 @@ public void setUp() { handler.setNonErrStream(testNonErrPrintStream); } - @After + @AfterEach public void tearDown() { AndHowLogHandler handler = (AndHowLogHandler) log.getHandlers()[0]; @@ -71,13 +74,13 @@ public void testTrace_String() { String sampleMsg = "This is a fact you don't care about"; log.trace(sampleMsg); - assertEquals("trace is off by default", 0, testNonErrByteArray.toString().length()); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testNonErrByteArray.toString().length(), "trace is off by default"); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); log.setLevel(Level.FINEST); log.trace(sampleMsg); assertTrue(testNonErrByteArray.toString().contains(sampleMsg)); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); // Complete reset tearDown(); @@ -89,8 +92,8 @@ public void testTrace_String() { log.setLevel(null); log.trace(sampleMsg); - assertEquals("trace is off by default", 0, testNonErrByteArray.toString().length()); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testNonErrByteArray.toString().length(), "trace is off by default"); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); System.setProperty(AndHowLogTest.class.getCanonicalName() + ".level", Level.FINEST.getName()); log.setLevel(null); @@ -104,13 +107,13 @@ public void testTrace_String() { @Test public void testTrace_String_ObjectArr() { log.trace("Some debug on line {0}, message ''{1}''", "42", "Bee Boo"); - assertEquals("trace is off by default", 0, testNonErrByteArray.toString().length()); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testNonErrByteArray.toString().length(), "trace is off by default"); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); log.setLevel(Level.FINEST); log.trace("Some debug on line {0}, message ''{1}''", "42", "Bee Boo"); assertTrue(testNonErrByteArray.toString().contains("Some debug on line 42, message 'Bee Boo'")); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); // Complete reset tearDown(); @@ -122,8 +125,8 @@ public void testTrace_String_ObjectArr() { log.setLevel(null); log.trace("Some debug on line {0}, message ''{1}''", "42", "Bee Boo"); - assertEquals("trace is off by default", 0, testNonErrByteArray.toString().length()); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testNonErrByteArray.toString().length(), "trace is off by default"); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); System.setProperty(AndHowLogTest.class.getCanonicalName() + ".level", Level.FINEST.getName()); log.setLevel(null); @@ -138,13 +141,13 @@ public void testTrace_String_ObjectArr() { public void testTrace_String_Throwable() { Exception ex = new Exception("ALERT!"); log.trace("It is nothing serious", ex); - assertEquals("trace is off by default", 0, testNonErrByteArray.toString().length()); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testNonErrByteArray.toString().length(), "trace is off by default"); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); log.setLevel(Level.FINEST); log.trace("It is nothing serious", ex); assertTrue(testNonErrByteArray.toString().contains("It is nothing serious")); assertTrue(testNonErrByteArray.toString().contains("ALERT!")); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); } /** @@ -153,13 +156,13 @@ public void testTrace_String_Throwable() { @Test public void testDebug_String() { log.debug("Hello!!!!"); - assertEquals("debug is off by default", 0, testNonErrByteArray.toString().length()); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testNonErrByteArray.toString().length(), "debug is off by default"); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); log.setLevel(Level.FINE); log.debug("Hello!!!!"); assertTrue(testNonErrByteArray.toString().contains("Hello!!!!")); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); // Complete reset tearDown(); @@ -172,13 +175,13 @@ public void testDebug_String() { @Test public void testDebug_String_ObjectArr() { log.debug("Some debug on line {0}, message ''{1}''", "42", "Bee Boo"); - assertEquals("debug is off by default", 0, testNonErrByteArray.toString().length()); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testNonErrByteArray.toString().length(), "debug is off by default"); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); log.setLevel(Level.FINE); log.debug("Some debug on line {0}, message ''{1}''", "42", "Bee Boo"); assertTrue(testNonErrByteArray.toString().contains("Some debug on line 42, message 'Bee Boo'")); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); // Complete reset tearDown(); setUp(); @@ -191,14 +194,14 @@ public void testDebug_String_ObjectArr() { public void testDebug_String_Throwable() { Exception ex = new Exception("ALERT!"); log.debug("It is nothing serious", ex); - assertEquals("debug is off by default", 0, testNonErrByteArray.toString().length()); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testNonErrByteArray.toString().length(), "debug is off by default"); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); log.setLevel(Level.FINE); log.debug("It is nothing serious", ex); assertTrue(testNonErrByteArray.toString().contains("It is nothing serious")); assertTrue(testNonErrByteArray.toString().contains("ALERT!")); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); // Complete reset tearDown(); setUp(); @@ -211,13 +214,13 @@ public void testDebug_String_Throwable() { public void testInfo_String() { log.setLevel(Level.OFF); log.info("Hello!!!!"); - assertEquals("Logging is off now", 0, testNonErrByteArray.toString().length()); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testNonErrByteArray.toString().length(), "Logging is off now"); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); log.setLevel(null); log.info("Hello!!!!"); assertTrue(testNonErrByteArray.toString().contains("Hello!!!!")); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); } /** @@ -227,13 +230,13 @@ public void testInfo_String() { public void testInfo_String_ObjectArr() { log.setLevel(Level.OFF); log.info("Some info on line {0}, message ''{1}''", "42", "Bee Boo"); - assertEquals("Logging is off now", 0, testNonErrByteArray.toString().length()); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testNonErrByteArray.toString().length(), "Logging is off now"); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); log.setLevel(null); log.info("Some info on line {0}, message ''{1}''", "42", "Bee Boo"); assertTrue(testNonErrByteArray.toString().contains("Some info on line 42, message 'Bee Boo'")); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); } /** @@ -244,14 +247,14 @@ public void testInfo_String_Throwable() { Exception ex = new Exception("ALERT!"); log.setLevel(Level.OFF); log.info("It is nothing serious", ex); - assertEquals("Logging is off now", 0, testNonErrByteArray.toString().length()); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testNonErrByteArray.toString().length(), "Logging is off now"); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); log.setLevel(null); log.info("It is nothing serious", ex); assertTrue(testNonErrByteArray.toString().contains("It is nothing serious")); assertTrue(testNonErrByteArray.toString().contains("ALERT!")); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); } /** @@ -261,13 +264,13 @@ public void testInfo_String_Throwable() { public void testWarn_String() { log.setLevel(Level.OFF); log.warn("Hello!!!!"); - assertEquals("Logging is off now", 0, testNonErrByteArray.toString().length()); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testNonErrByteArray.toString().length(), "Logging is off now"); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); log.setLevel(null); log.warn("Hello!!!!"); assertTrue(testNonErrByteArray.toString().contains("Hello!!!!")); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); } @@ -278,13 +281,13 @@ public void testWarn_String() { public void testWarn_String_ObjectArr() { log.setLevel(Level.OFF); log.warn("Some info on line {0}, message ''{1}''", "42", "Bee Boo"); - assertEquals("Logging is off now", 0, testNonErrByteArray.toString().length()); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testNonErrByteArray.toString().length(), "Logging is off now"); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); log.setLevel(null); log.warn("Some info on line {0}, message ''{1}''", "42", "Bee Boo"); assertTrue(testNonErrByteArray.toString().contains("Some info on line 42, message 'Bee Boo'")); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); } @@ -297,15 +300,15 @@ public void testWarn_String_Throwable() { log.setLevel(Level.OFF); log.warn("It cane be serious", ex); - assertEquals("Logging is off now", 0, testNonErrByteArray.toString().length()); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testNonErrByteArray.toString().length(), "Logging is off now"); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); log.setLevel(null); log.warn("It cane be serious", ex); assertTrue(testNonErrByteArray.toString().contains("It cane be serious")); assertTrue(testNonErrByteArray.toString().contains("ALERT!")); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); } @@ -316,13 +319,13 @@ public void testWarn_String_Throwable() { public void testError_String() { log.setLevel(Level.OFF); log.error("Oh-No!!!!"); - assertEquals("Logging is off now", 0, testNonErrByteArray.toString().length()); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testNonErrByteArray.toString().length(), "Logging is off now"); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); log.setLevel(null); log.error("Oh-No!!!!"); assertTrue(testErrByteArray.toString().contains("Oh-No!!!!")); - assertEquals("nonErr stream should be empty", 0, testNonErrByteArray.toString().length()); + assertEquals(0, testNonErrByteArray.toString().length(), "nonErr stream should be empty"); } /** @@ -332,13 +335,13 @@ public void testError_String() { public void testError_String_ObjectArr() { log.setLevel(Level.OFF); log.error("Big err on line {0}, message ''{1}''", "42", "Bee Boo"); - assertEquals("Logging is off now", 0, testNonErrByteArray.toString().length()); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testNonErrByteArray.toString().length(), "Logging is off now"); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); log.setLevel(null); log.error("Big err on line {0}, message ''{1}''", "42", "Bee Boo"); assertTrue(testErrByteArray.toString().contains("Big err on line 42, message 'Bee Boo'")); - assertEquals("nonErr stream should be empty", 0, testNonErrByteArray.toString().length()); + assertEquals(0, testNonErrByteArray.toString().length(), "nonErr stream should be empty"); } /** @@ -349,14 +352,14 @@ public void testError_String_Throwable() { log.setLevel(Level.OFF); Exception ex = new Exception("ALERT!"); log.error("Big Error!", ex); - assertEquals("Logging is off now", 0, testNonErrByteArray.toString().length()); - assertEquals("err stream should be empty", 0, testErrByteArray.toString().length()); + assertEquals(0, testNonErrByteArray.toString().length(), "Logging is off now"); + assertEquals(0, testErrByteArray.toString().length(), "err stream should be empty"); log.setLevel(null); log.error("Big Error!", ex); assertTrue(testErrByteArray.toString().contains("Big Error!")); assertTrue(testErrByteArray.toString().contains("ALERT!")); - assertEquals("nonErr stream should be empty", 0, testNonErrByteArray.toString().length()); + assertEquals(0, testNonErrByteArray.toString().length(), "nonErr stream should be empty"); } } diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/util/AndHowUtilTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/util/AndHowUtilTest.java index 2debf492..85ba7ba8 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/util/AndHowUtilTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/util/AndHowUtilTest.java @@ -2,13 +2,12 @@ */ package org.yarnandtail.andhow.util; -import org.junit.Test; +import org.junit.jupiter.api.Test; import java.lang.reflect.Method; -import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/util/IOUtilTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/util/IOUtilTest.java index 7db8f7aa..0fe529df 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/util/IOUtilTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/util/IOUtilTest.java @@ -2,8 +2,8 @@ */ package org.yarnandtail.andhow.util; -import org.junit.Test; -import static org.junit.Assert.*; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; import java.io.ByteArrayInputStream; import java.io.IOException; diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/util/NameUtilTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/util/NameUtilTest.java index 13e85890..fa48ed68 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/util/NameUtilTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/util/NameUtilTest.java @@ -1,12 +1,11 @@ package org.yarnandtail.andhow.util; -import org.yarnandtail.andhow.util.NameUtil; import java.util.Arrays; import java.util.List; -import org.junit.Before; -import org.junit.Test; -import static org.junit.Assert.*; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; /** * Naming support for Properties. diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/util/StackLocatorTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/util/StackLocatorTest.java index f7c496ab..bf80849d 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/util/StackLocatorTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/util/StackLocatorTest.java @@ -1,8 +1,7 @@ package org.yarnandtail.andhow.util; -import org.junit.Before; -import org.junit.Test; -import static org.junit.Assert.*; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; /** * This is testing production code from Log4J, so this is really just characterization. diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/util/TextUtilTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/util/TextUtilTest.java index 740efb96..5623760b 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/util/TextUtilTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/util/TextUtilTest.java @@ -4,9 +4,10 @@ import java.io.PrintStream; import java.nio.charset.StandardCharsets; import java.util.List; -import static org.junit.Assert.*; -import org.junit.Before; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.yarnandtail.andhow.api.ParsingException; /** * @@ -15,8 +16,9 @@ public class TextUtilTest { ByteArrayOutputStream baos; PrintStream ps; - - @Before + private static String FIND_INSTANCE_STRING_BROWNFOX = "The quick brown fox jumps over the lazy dog. The quick brown fox jumps over the lazy dog"; + + @BeforeEach public void setup() { baos = new ByteArrayOutputStream(); ps = new PrintStream(baos) { @@ -50,11 +52,14 @@ public void testFormat() { //Some more edge cases assertEquals("abc[[NULL]]xyz", TextUtil.format("abc{}xyz", (String)null)); assertEquals("abc[[NULL]]xyz", TextUtil.format("abc{}xyz", null, null, null)); + assertEquals("abcXXXdef[[NULL]]xyz", TextUtil.format("abc{}def{}xyz", "XXX", null, null)); } - @Test(expected = ArrayIndexOutOfBoundsException.class) + @Test public void testFormatException() { - assertEquals("abcXXXxyz", TextUtil.format("abc{}xyz{}", "XXX")); + assertThrows(ArrayIndexOutOfBoundsException.class, () -> + assertEquals("abcXXXxyz", TextUtil.format("abc{}xyz{}", "XXX")) + ); } @@ -70,6 +75,12 @@ public void testPatternsWithCurlyBraceLiteralsPrintln() { TextUtil.println(ps, "abc\\{}xyz", "XXX"); assertEquals("abc\\{}xyz", getStreamContent()); } + + @Test + public void testPatternWithBreak() { + TextUtil.println(ps, 5, "//", "abc{}def {}xyz", "XXX", "YYY"); + assertEquals("//abcXXXdef// YYYxyz", getStreamContent()); + } @Test public void testRepeat() { @@ -189,7 +200,28 @@ public void testWrapWithCompromiseBreakLocations() { assertEquals("i", result.get(1)); } - + + @Test + public void testWrapWithEmptyString() { + List result; + + result = TextUtil.wrap("", 10); + assertEquals(0, result.size()); + } + + @Test + public void testWrapWithNewlineChar() { + List result; + + result = TextUtil.wrap("\nabc", 10); + assertEquals(1, result.size()); + assertEquals("abc", result.get(0)); + + result = TextUtil.wrap("abc \ndefg hijklmnopqrstuvwxyzabcdefgh", 10); + assertEquals(2, result.size()); + assertEquals("abc", result.get(0)); + assertEquals("defg hijklmnopqrstuvwxyzabcdefgh", result.get(1)); + } @Test public void testWrapWithPrefix() { @@ -216,12 +248,35 @@ public void testWrapWithPrefix() { assertEquals("# xxxqrs tuv", result.get(2)); } + + @Test + public void testWrapNullPrefix() { + List result; + + result = TextUtil.wrap("abcdefghij klmnopqrstuvwxyz", 10, null, "xxx"); + assertEquals(2, result.size()); + assertEquals("abcdefghij", result.get(0)); + assertEquals("xxxklmnopqrstuvwxyz", result.get(1)); + } + + @Test + public void testWrapNullLineIndent() { + List result; + + result = TextUtil.wrap("abcdefghij klmnopqrstuvwxyz", 10, "# ", null); + assertEquals(2, result.size()); + assertEquals("# abcdefghij", result.get(0)); + assertEquals("# klmnopqrstuvwxyz", result.get(1)); + } @Test public void testEscapeXml() { assertEquals("<some text>", TextUtil.escapeXml("")); assertEquals("<some&text"", TextUtil.escapeXml(" + type.parse("") + ); + assertEquals(PARSE_ERROR_MSG, e.getMessage()); + } + + @Test + public void testParseNotANumber() throws ParsingException { + assertFalse(type.isParsable("apple")); + + Exception e = assertThrows(ParsingException.class, () -> + type.parse("apple") + ); + assertEquals(PARSE_ERROR_MSG, e.getMessage()); + } + + @Test + public void testCast() { + Object o = new BigDecimal("123.456"); + assertEquals(new BigDecimal("123.456"), o); + assertNotNull(type.cast(o)); + } + +} diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/valuetype/DblTypeTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/valuetype/DblTypeTest.java index af2d5c84..01a38282 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/valuetype/DblTypeTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/valuetype/DblTypeTest.java @@ -1,7 +1,7 @@ package org.yarnandtail.andhow.valuetype; -import static org.junit.Assert.*; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.api.ParsingException; /** @@ -38,18 +38,32 @@ public void testWeirdDoubleSpecialValues() throws ParsingException { } - @Test(expected=ParsingException.class) - public void testParseNotANumber() throws ParsingException { + @Test + public void testParseNotANumber() { DblType type = DblType.instance(); assertFalse(type.isParsable("apple")); - type.parse("apple"); + + assertThrows(ParsingException.class, () -> + type.parse("apple") + ); + } + + @Test + public void stringMarkedAsLongIsError() { + DblType type = DblType.instance(); + assertThrows(ParsingException.class, () -> + type.parse("34L") + ); } - @Test(expected=ParsingException.class) - public void testParseEmpty() throws ParsingException { + @Test + public void testParseEmpty() { DblType type = DblType.instance(); assertFalse(type.isParsable("")); - type.parse(""); + + assertThrows(ParsingException.class, () -> + type.parse("") + ); } @Test diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/valuetype/IntTypeTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/valuetype/IntTypeTest.java index bd4e1725..79dbe9ac 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/valuetype/IntTypeTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/valuetype/IntTypeTest.java @@ -2,8 +2,8 @@ */ package org.yarnandtail.andhow.valuetype; -import static org.junit.Assert.*; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.api.ParsingException; /** @@ -24,39 +24,54 @@ public void testParseHappyPath() throws ParsingException { assertNull(type.parse(null)); } - @Test(expected=ParsingException.class) - public void testParseDecimalNumber() throws ParsingException { + @Test + public void testParseDecimalNumber() { IntType type = IntType.instance(); assertFalse(type.isParsable("1234.1234")); - type.parse("1234.1234"); + + assertThrows(ParsingException.class, () -> + type.parse("1234.1234") + ); } - @Test(expected=ParsingException.class) - public void testParseNotANumber() throws ParsingException { + @Test + public void testParseNotANumber() { IntType type = IntType.instance(); assertFalse(type.isParsable("apple")); - type.parse("apple"); + + assertThrows(ParsingException.class, () -> + type.parse("apple") + ); } - @Test(expected=ParsingException.class) + @Test public void testParseEmpty() throws ParsingException { IntType type = IntType.instance(); assertFalse(type.isParsable("")); - type.parse(""); + + assertThrows(ParsingException.class, () -> + type.parse("") + ); } - @Test(expected=ParsingException.class) - public void testParseTooBig() throws ParsingException { + @Test + public void testParseTooBig() { IntType type = IntType.instance(); assertFalse(type.isParsable("9999999999999999999999999999999999999999")); - type.parse("9999999999999999999999999999999999999999"); + + assertThrows(ParsingException.class, () -> + type.parse("9999999999999999999999999999999999999999") + ); } - @Test(expected=ParsingException.class) - public void testParseTooSmall() throws ParsingException { + @Test + public void testParseTooSmall() { IntType type = IntType.instance(); assertFalse(type.isParsable("-9999999999999999999999999999999999999999")); - type.parse("-9999999999999999999999999999999999999999"); + + assertThrows(ParsingException.class, () -> + type.parse("-9999999999999999999999999999999999999999") + ); } @Test diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/valuetype/LngTypeTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/valuetype/LngTypeTest.java index eeb111ac..5342b70d 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/valuetype/LngTypeTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/valuetype/LngTypeTest.java @@ -1,7 +1,7 @@ package org.yarnandtail.andhow.valuetype; -import static org.junit.Assert.*; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.api.ParsingException; /** @@ -9,8 +9,6 @@ * @author ericeverman */ public class LngTypeTest { - - @Test public void testParseHappyPath() throws ParsingException { @@ -22,39 +20,62 @@ public void testParseHappyPath() throws ParsingException { assertNull(type.parse(null)); } - @Test(expected=ParsingException.class) - public void testParseDecimalNumber() throws ParsingException { + @Test + public void testParseDecimalNumber() { LngType type = LngType.instance(); assertFalse(type.isParsable("1234.1234")); - type.parse("1234.1234"); + + assertThrows(ParsingException.class, () -> + type.parse("1234.1234") + ); } - @Test(expected=ParsingException.class) + @Test public void testParseNotANumber() throws ParsingException { LngType type = LngType.instance(); assertFalse(type.isParsable("apple")); - type.parse("apple"); + + assertThrows(ParsingException.class, () -> + type.parse("apple") + ); } - @Test(expected=ParsingException.class) + @Test public void testParseEmpty() throws ParsingException { LngType type = LngType.instance(); assertFalse(type.isParsable("")); - type.parse(""); + + assertThrows(ParsingException.class, () -> + type.parse("") + ); + } + + @Test + public void stringMarkedWithLSufixIsError() { + LngType type = LngType.instance(); + assertThrows(ParsingException.class, () -> + type.parse("34L") + ); } - @Test(expected=ParsingException.class) - public void testParseTooBig() throws ParsingException { + @Test + public void testParseTooBig() { LngType type = LngType.instance(); assertFalse(type.isParsable("9999999999999999999999999999999999999999")); - type.parse("9999999999999999999999999999999999999999"); + + assertThrows(ParsingException.class, () -> + type.parse("9999999999999999999999999999999999999999") + ); } - @Test(expected=ParsingException.class) - public void testParseTooSmall() throws ParsingException { + @Test + public void testParseTooSmall() { LngType type = LngType.instance(); assertFalse(type.isParsable("-9999999999999999999999999999999999999999")); - type.parse("-9999999999999999999999999999999999999999"); + + assertThrows(ParsingException.class, () -> + type.parse("-9999999999999999999999999999999999999999") + ); } @Test diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/valuetype/LocalDateTimeTypeTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/valuetype/LocalDateTimeTypeTest.java index 6346917c..e122f022 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/valuetype/LocalDateTimeTypeTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/valuetype/LocalDateTimeTypeTest.java @@ -3,8 +3,8 @@ package org.yarnandtail.andhow.valuetype; import java.time.LocalDateTime; -import static org.junit.Assert.*; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.api.ParsingException; /** @@ -12,8 +12,6 @@ * @author ericeverman */ public class LocalDateTimeTypeTest { - - @Test public void testParseHappyPath() throws ParsingException { @@ -32,32 +30,44 @@ public void testParseHappyPath() throws ParsingException { assertNull(type.parse(null)); } - @Test(expected=ParsingException.class) - public void testParseTooManyDecimalPlaces() throws ParsingException { + @Test + public void testParseTooManyDecimalPlaces() { LocalDateTimeType type = LocalDateTimeType.instance(); assertFalse(type.isParsable("2007-12-03T10:15:30.1234567891")); - type.parse("2007-12-03T10:15:30.1234567891"); + + assertThrows(ParsingException.class, () -> + type.parse("2007-12-03T10:15:30.1234567891") + ); } - @Test(expected=ParsingException.class) - public void testParseEmpty() throws ParsingException { + @Test + public void testParseEmpty() { LocalDateTimeType type = LocalDateTimeType.instance(); assertFalse(type.isParsable("")); - type.parse(""); + + assertThrows(ParsingException.class, () -> + type.parse("") + ); } - @Test(expected=ParsingException.class) - public void testParseIncorrect24Hour() throws ParsingException { + @Test + public void testParseIncorrect24Hour() { LocalDateTimeType type = LocalDateTimeType.instance(); assertFalse(type.isParsable("2007-12-03T24:15:30.123456789")); - type.parse("2007-12-03T24:15:30.123456789"); + + assertThrows(ParsingException.class, () -> + type.parse("2007-12-03T24:15:30.123456789") + ); } - @Test(expected=ParsingException.class) - public void testParseMissingZeroPadding() throws ParsingException { + @Test + public void testParseMissingZeroPadding() { LocalDateTimeType type = LocalDateTimeType.instance(); assertFalse(type.isParsable("2007-12-03T10:15:3")); - type.parse("2007-12-03T10:15:3"); + + assertThrows(ParsingException.class, () -> + type.parse("2007-12-03T10:15:3") + ); } @Test diff --git a/andhow-core/src/test/java/org/yarnandtail/andhow/valuetype/StrTypeTest.java b/andhow-core/src/test/java/org/yarnandtail/andhow/valuetype/StrTypeTest.java index c6918b10..0ffd2155 100644 --- a/andhow-core/src/test/java/org/yarnandtail/andhow/valuetype/StrTypeTest.java +++ b/andhow-core/src/test/java/org/yarnandtail/andhow/valuetype/StrTypeTest.java @@ -2,8 +2,8 @@ */ package org.yarnandtail.andhow.valuetype; -import static org.junit.Assert.*; -import org.junit.Test; +import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.api.ParsingException; /** diff --git a/andhow-core/src/test/resources/org/yarnandtail/andhow/StdConfigSimulatedAppTest.all.props.speced.properties b/andhow-core/src/test/resources/org/yarnandtail/andhow/StdConfigSimulatedAppTest.all.props.speced.properties new file mode 100644 index 00000000..aae45645 --- /dev/null +++ b/andhow-core/src/test/resources/org/yarnandtail/andhow/StdConfigSimulatedAppTest.all.props.speced.properties @@ -0,0 +1,8 @@ +org.yarnandtail.andhow.StdConfigSimulatedAppTest.SampleRestClientGroup.APP_NAME = " Big App " +org.yarnandtail.andhow.StdConfigSimulatedAppTest.SampleRestClientGroup.REST_HOST = aquarius.usgs.gov +org.yarnandtail.andhow.StdConfigSimulatedAppTest.SampleRestClientGroup.REST_PORT = 8080 +org.yarnandtail.andhow.StdConfigSimulatedAppTest.SampleRestClientGroup.REST_SERVICE_NAME = doquery/ +org.yarnandtail.andhow.StdConfigSimulatedAppTest.SampleRestClientGroup.AUTH_KEY = abc123 +org.yarnandtail.andhow.StdConfigSimulatedAppTest.SampleRestClientGroup.RETRY_COUNT = 4 +org.yarnandtail.andhow.StdConfigSimulatedAppTest.SampleRestClientGroup.REQUEST_META_DATA = false +org.yarnandtail.andhow.StdConfigSimulatedAppTest.SampleRestClientGroup.REQUEST_SUMMARY_DATA = true \ No newline at end of file diff --git a/andhow-core/src/test/resources/org/yarnandtail/andhow/StdConfigSimulatedAppTest.invalid.properties b/andhow-core/src/test/resources/org/yarnandtail/andhow/StdConfigSimulatedAppTest.invalid.properties new file mode 100644 index 00000000..204d6ef7 --- /dev/null +++ b/andhow-core/src/test/resources/org/yarnandtail/andhow/StdConfigSimulatedAppTest.invalid.properties @@ -0,0 +1,7 @@ +org.yarnandtail.andhow.StdConfigSimulatedAppTest.SampleRestClientGroup.REST_HOST = aquarius.usgs.com +org.yarnandtail.andhow.StdConfigSimulatedAppTest.SampleRestClientGroup.REST_PORT = 999999 +org.yarnandtail.andhow.StdConfigSimulatedAppTest.SampleRestClientGroup.REST_SERVICE_NAME = doquery +org.yarnandtail.andhow.StdConfigSimulatedAppTest.SampleRestClientGroup.AUTH_KEY = abc123 +org.yarnandtail.andhow.StdConfigSimulatedAppTest.SampleRestClientGroup.RETRY_COUNT = NotReallyANumber +org.yarnandtail.andhow.StdConfigSimulatedAppTest.SampleRestClientGroup.REQUEST_META_DATA = false +org.yarnandtail.andhow.StdConfigSimulatedAppTest.SampleRestClientGroup.REQUEST_SUMMARY_DATA = true \ No newline at end of file diff --git a/andhow-core/src/test/resources/org/yarnandtail/andhow/StdConfigSimulatedAppTest.minimum.props.speced.properties b/andhow-core/src/test/resources/org/yarnandtail/andhow/StdConfigSimulatedAppTest.minimum.props.speced.properties new file mode 100644 index 00000000..06e5c1f7 --- /dev/null +++ b/andhow-core/src/test/resources/org/yarnandtail/andhow/StdConfigSimulatedAppTest.minimum.props.speced.properties @@ -0,0 +1,11 @@ +# +# Only required points are specified +# + +org.yarnandtail.andhow.StdConfigSimulatedAppTest.SampleRestClientGroup.REST_HOST = aquarius.usgs.gov +org.yarnandtail.andhow.StdConfigSimulatedAppTest.SampleRestClientGroup.REST_PORT = 8080 +# org.yarnandtail.andhow.StdConfigSimulatedAppTest.SampleRestClientGroup.REST_SERVICE_NAME = doquery +org.yarnandtail.andhow.StdConfigSimulatedAppTest.SampleRestClientGroup.AUTH_KEY = abc123 +# org.yarnandtail.andhow.StdConfigSimulatedAppTest.SampleRestClientGroup.RETRY_COUNT = 4 +# org.yarnandtail.andhow.StdConfigSimulatedAppTest.SampleRestClientGroup.REQUEST_META_DATA = false +# org.yarnandtail.andhow.StdConfigSimulatedAppTest.SampleRestClientGroup.REQUEST_SUMMARY_DATA = true \ No newline at end of file diff --git a/andhow-core/src/test/resources/org/yarnandtail/andhow/example/restclient/all.points.speced.properties b/andhow-core/src/test/resources/org/yarnandtail/andhow/example/restclient/all.points.speced.properties deleted file mode 100644 index db9f8fd7..00000000 --- a/andhow-core/src/test/resources/org/yarnandtail/andhow/example/restclient/all.points.speced.properties +++ /dev/null @@ -1,8 +0,0 @@ -org.yarnandtail.andhow.example.restclient.SampleRestClientGroup.APP_NAME = " Big App " -org.yarnandtail.andhow.example.restclient.SampleRestClientGroup.REST_HOST = aquarius.usgs.gov -org.yarnandtail.andhow.example.restclient.SampleRestClientGroup.REST_PORT = 8080 -org.yarnandtail.andhow.example.restclient.SampleRestClientGroup.REST_SERVICE_NAME = doquery/ -org.yarnandtail.andhow.example.restclient.SampleRestClientGroup.AUTH_KEY = abc123 -org.yarnandtail.andhow.example.restclient.SampleRestClientGroup.RETRY_COUNT = 4 -org.yarnandtail.andhow.example.restclient.SampleRestClientGroup.REQUEST_META_DATA = false -org.yarnandtail.andhow.example.restclient.SampleRestClientGroup.REQUEST_SUMMARY_DATA = true \ No newline at end of file diff --git a/andhow-core/src/test/resources/org/yarnandtail/andhow/example/restclient/invalid.properties b/andhow-core/src/test/resources/org/yarnandtail/andhow/example/restclient/invalid.properties deleted file mode 100644 index 340cac1a..00000000 --- a/andhow-core/src/test/resources/org/yarnandtail/andhow/example/restclient/invalid.properties +++ /dev/null @@ -1,7 +0,0 @@ -org.yarnandtail.andhow.example.restclient.SampleRestClientGroup.REST_HOST = aquarius.usgs.com -org.yarnandtail.andhow.example.restclient.SampleRestClientGroup.REST_PORT = 999999 -org.yarnandtail.andhow.example.restclient.SampleRestClientGroup.REST_SERVICE_NAME = doquery -org.yarnandtail.andhow.example.restclient.SampleRestClientGroup.AUTH_KEY = abc123 -org.yarnandtail.andhow.example.restclient.SampleRestClientGroup.RETRY_COUNT = NotReallyANumber -org.yarnandtail.andhow.example.restclient.SampleRestClientGroup.REQUEST_META_DATA = false -org.yarnandtail.andhow.example.restclient.SampleRestClientGroup.REQUEST_SUMMARY_DATA = true \ No newline at end of file diff --git a/andhow-core/src/test/resources/org/yarnandtail/andhow/example/restclient/minimum.points.speced.properties b/andhow-core/src/test/resources/org/yarnandtail/andhow/example/restclient/minimum.points.speced.properties deleted file mode 100644 index 7331d91c..00000000 --- a/andhow-core/src/test/resources/org/yarnandtail/andhow/example/restclient/minimum.points.speced.properties +++ /dev/null @@ -1,11 +0,0 @@ -# -# Only required points are specified -# - -org.yarnandtail.andhow.example.restclient.SampleRestClientGroup.REST_HOST = aquarius.usgs.gov -org.yarnandtail.andhow.example.restclient.SampleRestClientGroup.REST_PORT = 8080 -# org.yarnandtail.andhow.example.restclient.SampleRestClientGroup.REST_SERVICE_NAME = doquery -org.yarnandtail.andhow.example.restclient.SampleRestClientGroup.AUTH_KEY = abc123 -# org.yarnandtail.andhow.example.restclient.SampleRestClientGroup.RETRY_COUNT = 4 -# org.yarnandtail.andhow.example.restclient.SampleRestClientGroup.REQUEST_META_DATA = false -# org.yarnandtail.andhow.example.restclient.SampleRestClientGroup.REQUEST_SUMMARY_DATA = true \ No newline at end of file diff --git a/andhow-core/src/test/resources/org/yarnandtail/andhow/property/BigDecPropTest_badPath.properties b/andhow-core/src/test/resources/org/yarnandtail/andhow/property/BigDecPropTest_badPath.properties new file mode 100644 index 00000000..7a0d7317 --- /dev/null +++ b/andhow-core/src/test/resources/org/yarnandtail/andhow/property/BigDecPropTest_badPath.properties @@ -0,0 +1,7 @@ +org.yarnandtail.andhow.property.BigDecPropTest.BigDecGroup.NOT_NULL = 10.789 + +org.yarnandtail.andhow.property.BigDecPropTest.BigDecGroup.GREATER_THAN = -101.123 +org.yarnandtail.andhow.property.BigDecPropTest.BigDecGroup.GREATER_THAN_OR_EQUAL = -100.123456 + +org.yarnandtail.andhow.property.BigDecPropTest.BigDecGroup.LESS_THAN= 101.123 +org.yarnandtail.andhow.property.BigDecPropTest.BigDecGroup.LESS_THAN_OR_EQUAL = 101.123456 \ No newline at end of file diff --git a/andhow-core/src/test/resources/org/yarnandtail/andhow/property/BigDecPropTest_default.properties b/andhow-core/src/test/resources/org/yarnandtail/andhow/property/BigDecPropTest_default.properties new file mode 100644 index 00000000..99b8c968 --- /dev/null +++ b/andhow-core/src/test/resources/org/yarnandtail/andhow/property/BigDecPropTest_default.properties @@ -0,0 +1,5 @@ +# non-null property +org.yarnandtail.andhow.property.BigDecPropTest.BigDecGroup.NOT_NULL = 10.789 + +# test alias +alias = 101.123E+5 \ No newline at end of file diff --git a/andhow-core/src/test/resources/org/yarnandtail/andhow/property/BigDecPropTest_happyPath.properties b/andhow-core/src/test/resources/org/yarnandtail/andhow/property/BigDecPropTest_happyPath.properties new file mode 100644 index 00000000..7b8925a8 --- /dev/null +++ b/andhow-core/src/test/resources/org/yarnandtail/andhow/property/BigDecPropTest_happyPath.properties @@ -0,0 +1,11 @@ +# non-null property +org.yarnandtail.andhow.property.BigDecPropTest.BigDecGroup.NOT_NULL = 10.789 + +# greater than, greater than or equal, less than, less than or equal properties +org.yarnandtail.andhow.property.BigDecPropTest.BigDecGroup.GREATER_THAN = 101.123E+5 +org.yarnandtail.andhow.property.BigDecPropTest.BigDecGroup.GREATER_THAN_OR_EQUAL = 100.123456 +org.yarnandtail.andhow.property.BigDecPropTest.BigDecGroup.LESS_THAN= -1.01123E+7 +org.yarnandtail.andhow.property.BigDecPropTest.BigDecGroup.LESS_THAN_OR_EQUAL = 100.123456 + +# default property +org.yarnandtail.andhow.property.BigDecPropTest.BigDecGroup.DEFAULT = 789.789 \ No newline at end of file diff --git a/andhow-core/src/test/resources/org/yarnandtail/andhow/property/BigDecPropTest_missingProps.properties b/andhow-core/src/test/resources/org/yarnandtail/andhow/property/BigDecPropTest_missingProps.properties new file mode 100644 index 00000000..e69de29b diff --git a/andhow-testing/andhow-annotation-processor-test-harness/pom.xml b/andhow-testing/andhow-annotation-processor-test-harness/pom.xml index 49b1a85e..59f4312c 100644 --- a/andhow-testing/andhow-annotation-processor-test-harness/pom.xml +++ b/andhow-testing/andhow-annotation-processor-test-harness/pom.xml @@ -3,7 +3,7 @@ org.yarnandtail andhow-parent - 0.4.1-SNAPSHOT + 0.4.2-SNAPSHOT ../../pom.xml @@ -15,15 +15,28 @@ and creating and testing files in memory. - - junit - junit - ${project.groupId} andhow-core ${project.version} provided + + org.junit.jupiter + junit-jupiter-api + compile + true + + + + + maven-deploy-plugin + + true + + + + + diff --git a/andhow-testing/andhow-annotation-processor-test-harness/src/main/java/org/yarnandtail/compile/AndHowCompileProcessorTestBase.java b/andhow-testing/andhow-annotation-processor-test-harness/src/main/java/org/yarnandtail/compile/AndHowCompileProcessorTestBase.java new file mode 100644 index 00000000..5c1bd698 --- /dev/null +++ b/andhow-testing/andhow-annotation-processor-test-harness/src/main/java/org/yarnandtail/compile/AndHowCompileProcessorTestBase.java @@ -0,0 +1,83 @@ +package org.yarnandtail.compile; + +import java.util.HashSet; +import java.util.Set; +import javax.tools.DiagnosticCollector; +import javax.tools.JavaCompiler; +import javax.tools.JavaFileObject; +import javax.tools.ToolProvider; +import org.junit.jupiter.api.BeforeEach; +import org.yarnandtail.andhow.util.IOUtil; + +/** + * + * @author ericevermanpersonal + */ +public class AndHowCompileProcessorTestBase { + + /** Classpath of the generated service file for AndHow property registration */ + protected static final String REGISTRAR_SVS_PATH = + "/META-INF/services/org.yarnandtail.andhow.service.PropertyRegistrar"; + protected static final String INIT_SVS_PATH = "/META-INF/services/org.yarnandtail.andhow.AndHowInit"; + protected static final String TEST_INIT_SVS_PATH = "/META-INF/services/org.yarnandtail.andhow.AndHowTestInit"; + + protected JavaCompiler compiler; + protected MemoryFileManager manager; + protected DiagnosticCollector diagnostics; + protected TestClassLoader loader; + + protected Set sources; //New set of source files to compile + + @BeforeEach + public void setupTest() { + compiler = ToolProvider.getSystemJavaCompiler(); + manager = new MemoryFileManager(compiler); + diagnostics = new DiagnosticCollector(); + loader = new TestClassLoader(manager); + sources = new HashSet(); + } + + /** + * The source path to where to find this file on the classpath + * @param classPackage + * @param simpleClassName + * @return + */ + public String srcPath(String classPackage, String simpleClassName) { + return "/" + classPackage.replace(".", "/") + "/" + simpleClassName + ".java"; + } + + /** + * Full canonical name of the class. + * @param classPackage + * @param simpleClassName + * @return + */ + public String fullName(String classPackage, String simpleClassName) { + return classPackage + "." + simpleClassName; + } + + /** + * Builds a new TestSource object for a Java source file on the classpath + * @param classPackage + * @param simpleClassName + * @return + * @throws Exception + */ + public TestSource buildTestSource(String classPackage, String simpleClassName) throws Exception { + String classContent = IOUtil.getUTF8ResourceAsString(srcPath(classPackage, simpleClassName)); + return new TestSource(fullName(classPackage, simpleClassName), JavaFileObject.Kind.SOURCE, classContent); + } + + /** + * Build the canonical name of the generated class from the source class. + * + * @param classPackage + * @param simpleClassName + * @return + */ + public String genName(String classPackage, String simpleClassName) { + return classPackage + ".$" + simpleClassName + "_AndHowProps"; + } + +} diff --git a/andhow-testing/andhow-annotation-processor-test-harness/src/test/java/org/yarnandtail/compile/MemoryFileManagerTest.java b/andhow-testing/andhow-annotation-processor-test-harness/src/test/java/org/yarnandtail/compile/MemoryFileManagerTest.java index 5de01947..b78a0408 100644 --- a/andhow-testing/andhow-annotation-processor-test-harness/src/test/java/org/yarnandtail/compile/MemoryFileManagerTest.java +++ b/andhow-testing/andhow-annotation-processor-test-harness/src/test/java/org/yarnandtail/compile/MemoryFileManagerTest.java @@ -5,13 +5,11 @@ import java.io.IOException; import java.io.Writer; import javax.tools.*; -import javax.tools.JavaFileManager.Location; -import org.yarnandtail.compile.MemoryFileManager; -import org.yarnandtail.compile.TestClassLoader; -import org.junit.Before; -import org.junit.Test; -import static org.junit.Assert.*; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; /** * @@ -26,7 +24,7 @@ public class MemoryFileManagerTest { public MemoryFileManagerTest() { } - @Before + @BeforeEach public void setUp() { compiler = ToolProvider.getSystemJavaCompiler(); manager = new MemoryFileManager(compiler); diff --git a/andhow-testing/andhow-annotation-processor-tests/pom.xml b/andhow-testing/andhow-annotation-processor-tests/pom.xml index f7b992e9..94e90608 100644 --- a/andhow-testing/andhow-annotation-processor-tests/pom.xml +++ b/andhow-testing/andhow-annotation-processor-tests/pom.xml @@ -3,7 +3,7 @@ org.yarnandtail andhow-parent - 0.4.1-SNAPSHOT + 0.4.2-SNAPSHOT ../../pom.xml @@ -34,13 +34,6 @@ ${project.version} provided - - - - junit - junit - - diff --git a/andhow-testing/andhow-annotation-processor-tests/src/test/java/org/yarnandtail/andhow/service/PropertyRegistrarLoaderTest.java b/andhow-testing/andhow-annotation-processor-tests/src/test/java/org/yarnandtail/andhow/service/PropertyRegistrarLoaderTest.java index a97661dd..1a3a92c4 100644 --- a/andhow-testing/andhow-annotation-processor-tests/src/test/java/org/yarnandtail/andhow/service/PropertyRegistrarLoaderTest.java +++ b/andhow-testing/andhow-annotation-processor-tests/src/test/java/org/yarnandtail/andhow/service/PropertyRegistrarLoaderTest.java @@ -1,12 +1,13 @@ package org.yarnandtail.andhow.service; import java.util.List; -import org.junit.Test; + +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.internal.NameAndProperty; import org.yarnandtail.classvistests.sample.NonStaticInnerClassSample; import org.yarnandtail.classvistests.sample.PropertySample; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; import org.yarnandtail.andhow.api.GroupProxy; diff --git a/andhow-testing/andhow-annotation-processor-tests/src/test/java/org/yarnandtail/classvistests/nonapp/sample/VisibilitySampleTest.java b/andhow-testing/andhow-annotation-processor-tests/src/test/java/org/yarnandtail/classvistests/nonapp/sample/VisibilitySampleTest.java index 3297ee54..533b3f50 100644 --- a/andhow-testing/andhow-annotation-processor-tests/src/test/java/org/yarnandtail/classvistests/nonapp/sample/VisibilitySampleTest.java +++ b/andhow-testing/andhow-annotation-processor-tests/src/test/java/org/yarnandtail/classvistests/nonapp/sample/VisibilitySampleTest.java @@ -2,9 +2,9 @@ import java.lang.reflect.Field; import java.lang.reflect.Modifier; -import org.junit.Test; +import org.junit.jupiter.api.Test; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * These are some characterization tests of how visibility of non-public diff --git a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep1/pom.xml b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep1/pom.xml index 7f0b8de4..90eedebb 100755 --- a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep1/pom.xml +++ b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep1/pom.xml @@ -3,7 +3,7 @@ org.yarnandtail andhow-multimodule-dataprocess - 0.4.1-SNAPSHOT + 0.4.2-SNAPSHOT andhow-default-behavior-dep1 @@ -14,25 +14,6 @@ Sample to be used as a compiled jar by the andhow-default-behavior-test to test property behavior in external compiled libraries. - - - - - org.yarnandtail - andhow - ${project.version} - - - - org.yarnandtail - andhow-test-harness - ${project.version} - test - - - junit - junit - - + diff --git a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep1/src/test/java/com/dep1/EarthMapMakerNotUsingAHBaseTestClassTest.java b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep1/src/test/java/com/dep1/EarthMapMakerNotUsingAHBaseTestClassTest.java index f9c3ad71..e8ea8766 100644 --- a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep1/src/test/java/com/dep1/EarthMapMakerNotUsingAHBaseTestClassTest.java +++ b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep1/src/test/java/com/dep1/EarthMapMakerNotUsingAHBaseTestClassTest.java @@ -1,7 +1,7 @@ package com.dep1; -import org.junit.Test; -import static org.junit.Assert.*; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; /** * diff --git a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep1/src/test/java/com/dep1/EarthMapMakerUsingAHBaseTestClassTest.java b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep1/src/test/java/com/dep1/EarthMapMakerUsingAHBaseTestClassTest.java index 7eb866b1..38770de4 100644 --- a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep1/src/test/java/com/dep1/EarthMapMakerUsingAHBaseTestClassTest.java +++ b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep1/src/test/java/com/dep1/EarthMapMakerUsingAHBaseTestClassTest.java @@ -1,12 +1,12 @@ package com.dep1; -import org.junit.FixMethodOrder; -import org.junit.Test; -import org.junit.runners.MethodSorters; -import org.yarnandtail.andhow.AndHowTestBase; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.MethodOrderer; +import static org.junit.jupiter.api.Assertions.*; +import org.yarnandtail.andhow.AndHowJunit5TestBase; import org.yarnandtail.andhow.NonProductionConfig; -import static org.junit.Assert.*; /** * Note that these test methods are specified to be executed in Alph sort order @@ -14,8 +14,8 @@ * * @author ericeverman */ -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -public class EarthMapMakerUsingAHBaseTestClassTest extends AndHowTestBase { +@TestMethodOrder(MethodOrderer.MethodName.class) +public class EarthMapMakerUsingAHBaseTestClassTest extends AndHowJunit5TestBase { public EarthMapMakerUsingAHBaseTestClassTest() { } diff --git a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep2/pom.xml b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep2/pom.xml index 4d6b8d65..ed4b2157 100644 --- a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep2/pom.xml +++ b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep2/pom.xml @@ -4,7 +4,7 @@ org.yarnandtail andhow-multimodule-dataprocess - 0.4.1-SNAPSHOT + 0.4.2-SNAPSHOT andhow-default-behavior-dep2 jar @@ -12,24 +12,5 @@ andhow-default-behavior-test to test property behavior in external compiled libraries. - - - - org.yarnandtail - andhow - ${project.version} - - - - org.yarnandtail - andhow-test-harness - ${project.version} - test - - - junit - junit - - \ No newline at end of file diff --git a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep2/src/test/java/com/dep2/MarsMapMakerNotUsingAHBaseTestClassTest.java b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep2/src/test/java/com/dep2/MarsMapMakerNotUsingAHBaseTestClassTest.java index 051d3f9c..026ec253 100644 --- a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep2/src/test/java/com/dep2/MarsMapMakerNotUsingAHBaseTestClassTest.java +++ b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep2/src/test/java/com/dep2/MarsMapMakerNotUsingAHBaseTestClassTest.java @@ -1,7 +1,7 @@ package com.dep2; -import org.junit.Test; -import static org.junit.Assert.*; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; /** * diff --git a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep2/src/test/java/com/dep2/MarsMapMakerUsingAHBaseTestClassTest.java b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep2/src/test/java/com/dep2/MarsMapMakerUsingAHBaseTestClassTest.java index fcca940f..8cc8a8ec 100644 --- a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep2/src/test/java/com/dep2/MarsMapMakerUsingAHBaseTestClassTest.java +++ b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-dep2/src/test/java/com/dep2/MarsMapMakerUsingAHBaseTestClassTest.java @@ -1,12 +1,12 @@ package com.dep2; -import org.junit.FixMethodOrder; -import org.junit.Test; -import org.junit.runners.MethodSorters; -import org.yarnandtail.andhow.AndHowTestBase; -import org.yarnandtail.andhow.NonProductionConfig; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.MethodOrderer; +import static org.junit.jupiter.api.Assertions.*; -import static org.junit.Assert.*; +import org.yarnandtail.andhow.AndHowJunit5TestBase; +import org.yarnandtail.andhow.NonProductionConfig; /** * Note that these test methods are specified to be executed in Alph sort order @@ -14,8 +14,8 @@ * * @author ericeverman */ -@FixMethodOrder(MethodSorters.NAME_ASCENDING) -public class MarsMapMakerUsingAHBaseTestClassTest extends AndHowTestBase { +@TestMethodOrder(MethodOrderer.MethodName.class) +public class MarsMapMakerUsingAHBaseTestClassTest extends AndHowJunit5TestBase { public MarsMapMakerUsingAHBaseTestClassTest() { } diff --git a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/pom.xml b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/pom.xml index 097c0780..14712a46 100755 --- a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/pom.xml +++ b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/pom.xml @@ -3,7 +3,7 @@ org.yarnandtail andhow-multimodule-dataprocess - 0.4.1-SNAPSHOT + 0.4.2-SNAPSHOT andhow-default-behavior-test @@ -24,22 +24,6 @@ andhow-default-behavior-dep2 ${project.version} - - - org.yarnandtail - andhow - ${project.version} - - - ${project.groupId} - andhow-test-harness - ${project.version} - test - - - junit - junit - org.springframework spring-test diff --git a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/src/test/java/com/dep1/EarthMapMakerTest.java b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/src/test/java/com/dep1/EarthMapMakerTest.java index d2bd0282..68d8729d 100644 --- a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/src/test/java/com/dep1/EarthMapMakerTest.java +++ b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/src/test/java/com/dep1/EarthMapMakerTest.java @@ -1,29 +1,33 @@ package com.dep1; import org.dataprocess.ExternalServiceConnector; -import org.junit.Test; import org.springframework.mock.jndi.SimpleNamingContextBuilder; import org.yarnandtail.andhow.*; import org.yarnandtail.andhow.api.AppFatalException; import org.yarnandtail.andhow.internal.LoaderProblem; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; /** * * @author ericeverman */ -public class EarthMapMakerTest extends AndHowTestBase { +public class EarthMapMakerTest extends AndHowJunit5TestBase { + /** + * This test will see the properties set in the andhow.properties file on the classpath. + * Currently that file is at: /src/main/resources/andhow.properties
    + * Optionally, an set of properties used only during testing could be placed at: + * /src/test/resources/andhow.properties
    + * That file would then be used exclusively during testing. + */ @Test public void testConfigFromPropertiesFileOnly() { EarthMapMaker emm = new EarthMapMaker(); - //Actual values + //These values match what is loaded from the andhow.properties file assertEquals("My Map", emm.getMapName()); assertEquals(-125, emm.getWestBound()); assertEquals(51, emm.getNorthBound()); @@ -34,62 +38,44 @@ public void testConfigFromPropertiesFileOnly() { assertEquals("http://prod.mybiz.com.logger/EarthMapMaker/", emm.getLogServerUrl()); } - - @Test - public void testConfigFromSysProps() { - - System.setProperty("com.dep1.EarthMapMaker.MAP_NAME", "SysPropMapName"); - System.setProperty("com.dep1.EarthMapMaker.EAST_BOUND", "-99"); - - NonProductionConfig.instance().forceBuild(); - - EarthMapMaker emm = new EarthMapMaker(); - - //Actual values - assertEquals("SysPropMapName", emm.getMapName()); - assertEquals(-99, emm.getEastBound()); - } - + + /** + * Sometimes a test will need custom configuration, such as wanting to + * test your app when it is configured a particular way. + * This example does that for just this one method. + * + * By using the base class AndHowJunit5TestBase, AndHow + * configuration will automatically revert to its original state + * (configured via the property file andhow.properties) + * when this method completes. + */ @Test - public void testConfigFromCmdLineThenSysPropsViaNonProdConfigForceBuild() { - - System.setProperty("com.dep1.EarthMapMaker.MAP_NAME", "SysPropMapName"); - System.setProperty("com.dep1.EarthMapMaker.EAST_BOUND", "-99"); - + public void testUsingCustomConfiguration() { - NonProductionConfig.instance().setCmdLineArgs(new String[] { - "com.dep1.EarthMapMaker.MAP_NAME=CmdLineMapName", - "com.dep1.EarthMapMaker.WEST_BOUND=-179"}) + NonProductionConfig.instance() + .addFixedValue(EarthMapMaker.MAP_NAME, "SomeNameILike") /* via Property reference */ + .addFixedValue("com.dep1.EarthMapMaker.EAST_BOUND", 42) /* via Property name */ .forceBuild(); - - EarthMapMaker emm = new EarthMapMaker(); - - //Actual values - assertEquals("CmdLineMapName", emm.getMapName()); - assertEquals(-99, emm.getEastBound()); - assertEquals(-179, emm.getWestBound()); - } - - @Test - public void testConfigFromCmdLineThenSysPropsViaProdConfigUtilForceBuild() { - - System.setProperty("com.dep1.EarthMapMaker.MAP_NAME", "SysPropMapName"); - System.setProperty("com.dep1.EarthMapMaker.EAST_BOUND", "-99"); - - AndHowNonProductionUtil.forceRebuild( - AndHow.findConfig().setCmdLineArgs(new String[] { - "com.dep1.EarthMapMaker.MAP_NAME=CmdLineMapName", - "com.dep1.EarthMapMaker.WEST_BOUND=-179"}) - ); - + EarthMapMaker emm = new EarthMapMaker(); - - //Actual values - assertEquals("CmdLineMapName", emm.getMapName()); - assertEquals(-99, emm.getEastBound()); - assertEquals(-179, emm.getWestBound()); + + //These values were customized above + assertEquals("SomeNameILike", emm.getMapName()); + assertEquals(42, emm.getEastBound()); + + //All other values still come from the andhow.properties file + assertEquals(51, emm.getNorthBound()); } - + + + /** + * This just demonstrates the order of loading. + * The System Properties Loader is earlier in the loader list than the + * JNDI loader, thus, values set as System Properties 'win' and override + * values found in JNDI. Properties files are read last of all. + * + * @throws Exception + */ @Test public void testOrderOfLoading() throws Exception { @@ -102,6 +88,8 @@ public void testOrderOfLoading() throws Exception { jndi.bind("java:" + "com.dep1.EarthMapMaker.SOUTH_BOUND", "7"); jndi.bind("java:comp/env/" + "org.dataprocess.ExternalServiceConnector.ConnectionConfig.SERVICE_URL", "test/"); jndi.activate(); + + NonProductionConfig.instance().forceBuild(); //VALUES IN THE PROPS FILE //org.dataprocess.ExternalServiceConnector.ConnectionConfig.SERVICE_URL = http://forwardcorp.com/service/ @@ -124,25 +112,19 @@ public void testOrderOfLoading() throws Exception { assertEquals(-99, emm.getEastBound()); assertEquals(7, emm.getSouthBound()); } - + + /** + * Validation is always enforced + */ @Test - public void testBadInt() { - - System.setProperty("com.dep1.EarthMapMaker.MAP_NAME", "SysPropMapName"); - System.setProperty("com.dep1.EarthMapMaker.EAST_BOUND", "East"); - - try { + public void testAttemptToAssignAnInvalidValue() { + + //LOG_SERVER must start w/ "http://" + assertThrows(AppFatalException.class, () ->{ NonProductionConfig.instance() - .setCmdLineArgs(new String[] { - "com.dep1.EarthMapMaker.MAP_NAME=CmdLineMapName", - "com.dep1.EarthMapMaker.WEST_BOUND=-179"}) + .addFixedValue("com.dep1.EarthMapMaker.LOG_SERVER", "ftp://blah/") .forceBuild(); - - fail("Expected an exception"); - } catch (AppFatalException afe) { - assertEquals(1, afe.getProblems().size()); - assertTrue(afe.getProblems().get(0) instanceof LoaderProblem.StringConversionLoaderProblem); - } + }); } } diff --git a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/src/test/java/com/dep2/MarsMapMakerTest.java b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/src/test/java/com/dep2/MarsMapMakerTest.java index 00a46c60..e7d55601 100644 --- a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/src/test/java/com/dep2/MarsMapMakerTest.java +++ b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/src/test/java/com/dep2/MarsMapMakerTest.java @@ -1,31 +1,39 @@ package com.dep2; -import com.dep2.*; import org.dataprocess.ExternalServiceConnector; -import org.junit.Test; import org.springframework.mock.jndi.SimpleNamingContextBuilder; import org.yarnandtail.andhow.*; import org.yarnandtail.andhow.api.AppFatalException; import org.yarnandtail.andhow.internal.LoaderProblem; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; /** * * @author ericeverman */ -public class MarsMapMakerTest extends AndHowTestBase { +public class MarsMapMakerTest extends AndHowJunit5TestBase { + /** + * Its OK to do nothing. AndHow will initialize itself at the point + * of the first Property value access. + * + * AndHowJunit5TestBase allows you to modify AndHow's state + * for each test class and/or for each test method - something + * that is NOT possible in production. + * If you use the AndHowJunit5TestBase for your tests, any changes + * you make to AndHow's state in a test class or during a test + * method are reverted after each method / test class completes. + */ @Test public void testConfigFromPropertiesFileOnly() { MarsMapMaker mmm = new MarsMapMaker(); - - //Actual values - assertEquals("My Map", mmm.getMapName()); + + //These values read from andhow.properties - see the EarthMapMakerTest for + //more details. + assertEquals("My Map", mmm.getMapName()); //AndHow initializes here if it hasn't already. assertEquals(-125, mmm.getWestBound()); assertEquals(51, mmm.getNorthBound()); assertEquals(-65, mmm.getEastBound()); @@ -35,95 +43,5 @@ public void testConfigFromPropertiesFileOnly() { assertEquals("http://prod.mybiz.com.logger/MarsMapMaker/", mmm.getLogServerUrl()); } - - @Test - public void testConfigFromSysProps() { - - System.setProperty("com.dep2.MarsMapMaker.MAP_NAME", "SysPropMapName"); - System.setProperty("com.dep2.MarsMapMaker.EAST_BOUND", "-99"); - - NonProductionConfig.instance().forceBuild(); - - MarsMapMaker mmm = new MarsMapMaker(); - - //Actual values - assertEquals("SysPropMapName", mmm.getMapName()); - assertEquals(-99, mmm.getEastBound()); - } - - @Test - public void testConfigFromCmdLineThenSysProps() { - - System.setProperty("com.dep2.MarsMapMaker.MAP_NAME", "SysPropMapName"); - System.setProperty("com.dep2.MarsMapMaker.EAST_BOUND", "-99"); - - - NonProductionConfig.instance().setCmdLineArgs(new String[] { - "com.dep2.MarsMapMaker.MAP_NAME=CmdLineMapName", - "com.dep2.MarsMapMaker.WEST_BOUND=-179"}) - .forceBuild(); - - MarsMapMaker mmm = new MarsMapMaker(); - - //Actual values - assertEquals("CmdLineMapName", mmm.getMapName()); - assertEquals(-99, mmm.getEastBound()); - assertEquals(-179, mmm.getWestBound()); - } - - @Test - public void testOrderOfLoading() throws Exception { - - System.setProperty("com.dep2.MarsMapMaker.MAP_NAME", "SysPropMapName"); - System.setProperty("com.dep2.MarsMapMaker.EAST_BOUND", "-99"); - - - SimpleNamingContextBuilder jndi = getJndi(); - jndi.bind("java:" + "com.dep2.MarsMapMaker.MAP_NAME", "JndiPropMapName"); - jndi.bind("java:" + "com.dep2.MarsMapMaker.SOUTH_BOUND", "7"); - jndi.bind("java:comp/env/" + "org.dataprocess.ExternalServiceConnector.ConnectionConfig.SERVICE_URL", "test/"); - jndi.activate(); - - //VALUES IN THE PROPS FILE - //org.dataprocess.ExternalServiceConnector.ConnectionConfig.SERVICE_URL = http://forwardcorp.com/service/ - //org.dataprocess.ExternalServiceConnector.ConnectionConfig.TIMEOUT = 60 - //com.dep2.MarsMapMaker.EAST_BOUND = -65 - //com.dep2.MarsMapMaker.MAP_NAME = My Map - //com.dep2.MarsMapMaker.NORTH_BOUND = 51 - //com.dep2.MarsMapMaker.SOUTH_BOUND = 23 - //com.dep2.MarsMapMaker.WEST_BOUND = -125 - - - ExternalServiceConnector esc = new ExternalServiceConnector(); - assertEquals("test/", esc.getConnectionUrl()); - assertEquals(60, esc.getConnectionTimeout()); - - MarsMapMaker mmm = new MarsMapMaker(); - assertEquals("SysPropMapName", mmm.getMapName()); - assertEquals(-125, mmm.getWestBound()); - assertEquals(51, mmm.getNorthBound()); - assertEquals(-99, mmm.getEastBound()); - assertEquals(7, mmm.getSouthBound()); - } - - @Test - public void testBadInt() { - - System.setProperty("com.dep2.MarsMapMaker.MAP_NAME", "SysPropMapName"); - System.setProperty("com.dep2.MarsMapMaker.EAST_BOUND", "East"); - - try { - NonProductionConfig.instance() - .setCmdLineArgs(new String[] { - "com.dep2.MarsMapMaker.MAP_NAME=CmdLineMapName", - "com.dep2.MarsMapMaker.WEST_BOUND=-179"}) - .forceBuild(); - - fail("Expected an exception"); - } catch (AppFatalException afe) { - assertEquals(1, afe.getProblems().size()); - assertTrue(afe.getProblems().get(0) instanceof LoaderProblem.StringConversionLoaderProblem); - } - } } diff --git a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/src/test/java/org/dataprocess/ExternalServiceConnectorTest.java b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/src/test/java/org/dataprocess/ExternalServiceConnectorTest.java index 9715aff7..b17c0d58 100644 --- a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/src/test/java/org/dataprocess/ExternalServiceConnectorTest.java +++ b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/andhow-default-behavior-test/src/test/java/org/dataprocess/ExternalServiceConnectorTest.java @@ -1,23 +1,20 @@ package org.dataprocess; -import org.junit.Test; -import org.yarnandtail.andhow.AndHowTestBase; +import org.yarnandtail.andhow.AndHowJunit5TestBase; -import static org.junit.Assert.*; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; /** + * The EarthMapMakerTest has more complete examples of testing. * * @author ericeverman */ -public class ExternalServiceConnectorTest extends AndHowTestBase { +public class ExternalServiceConnectorTest extends AndHowJunit5TestBase { public ExternalServiceConnectorTest() { } - - /** - * Test of getConnectionUrl method, of class EarthMapMaker. - */ @Test public void testAllConfigValues() { ExternalServiceConnector esc = new ExternalServiceConnector(); diff --git a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/pom.xml b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/pom.xml index 31ca1b7d..a903aeb0 100644 --- a/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/pom.xml +++ b/andhow-testing/andhow-simulated-app-tests/andhow-multimodule-dataprocess/pom.xml @@ -4,7 +4,7 @@ org.yarnandtail andhow-simulated-app-tests - 0.4.1-SNAPSHOT + 0.4.2-SNAPSHOT andhow-multimodule-dataprocess pom @@ -14,4 +14,19 @@ andhow-default-behavior-dep1 andhow-default-behavior-dep2 + + + + + org.yarnandtail + andhow + ${project.version} + + + org.yarnandtail + andhow-test-harness + ${project.version} + test + + \ No newline at end of file diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-1/pom.xml b/andhow-testing/andhow-simulated-app-tests/example-app-1/pom.xml new file mode 100644 index 00000000..ffa8b852 --- /dev/null +++ b/andhow-testing/andhow-simulated-app-tests/example-app-1/pom.xml @@ -0,0 +1,45 @@ + + + 4.0.0 + + andhow-simulated-app-tests + org.yarnandtail + 0.4.2-SNAPSHOT + + example-app-1 + + + + + org.yarnandtail + andhow + ${project.version} + + + org.yarnandtail + andhow-test-harness + ${project.version} + + + + app + + + maven-assembly-plugin + + + + com.bigcorp.Checker + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + \ No newline at end of file diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-1/src/main/java/com/bigcorp/Calculator.java b/andhow-testing/andhow-simulated-app-tests/example-app-1/src/main/java/com/bigcorp/Calculator.java new file mode 100644 index 00000000..2f61273c --- /dev/null +++ b/andhow-testing/andhow-simulated-app-tests/example-app-1/src/main/java/com/bigcorp/Calculator.java @@ -0,0 +1,91 @@ +package com.bigcorp; + +import org.yarnandtail.andhow.property.StrProp; + +/** + * A simple calculator that can use two different modes, which an AndHow {@Code StrProp} allows + * selection of based on configuration from (in this case) a properties file. + * + * This isn't a complete application, but its easy to imagine {@Code doCalc} being called by an + * AWS Lambda function, part of a command line utility, or just some library part of a larger app. + */ +public class Calculator { + + //The AndHow configuration Property to select between two modes (doesn't have to be public) + public static final StrProp MODE = StrProp.builder() + .mustBeNonNull().mustEqual("DOUBLE", "FLOAT").build(); + + /** + * Do the calculation, but choose which implementation to use based on CALC_MODE + * @param a + * @param b + * @return + */ + public Number doCalc(Number a, Number b) { + + Number result = null; + + if (MODE.getValue().equals("DOUBLE")) { + result = doDoubleCalc(a, b); + } else if (MODE.getValue().equals("FLOAT")) { + result = doFloatCalc(a, b); + } else { + //throw new IllegalStateException("Validation on CALC_MODE ensures this never happens"); + } + + return result; + } + + protected Number doDoubleCalc(Number a, Number b) { + return a.doubleValue() / b.doubleValue(); + } + + protected Number doFloatCalc(Number a, Number b) { + return a.floatValue() / b.floatValue(); + } + + /** + * A main method to run this app as it would be in a 'production' environment. + * Run it from an IDE (configure the IDE to pass two numbers as args), or via command line. + *

    + * To run from command line, first use Maven to create a runnable jar. + * Here are the commands, executed from the root of the AndHow project, to build and run the jar:
    + *

    {@Code
    +	 * > mvn clean package -DskipTests -Dmaven.javadoc.skip=true
    +	 * > java -jar andhow-testing/andhow-simulated-app-tests/example-app-1/target/app.jar 1.23 4.56
    +	 * }
    + * The output of running this command will be: + *
    {@Code
    +	 * Result is 0.26973684210526316 (Double)
    +	 * }
    + * How did it know to use the Double implementation? AndHow finds the {@Code checker.production.properties} + * file on the classpath and reads the configured value for CALC_MODE. {@CODE CALC_MODE.getValue()} + * returns 'DOUBLE'. Compare this to the unit test for this class... + *

    + * Properties files are just one way AndHow reads configuration. Values in the properties file + * could be overwritten via env. vars., JNDI, system properties, etc.. + * Rerunning the main method with a system property like this: + *

    {@Code
    +	 * > java -Dcom.bigcorp.Calculator.CALC_MODE=FLOAT -jar andhow-testing/andhow-simulated-app-tests/example-app-1/target/app.jar 1.23 4.56
    +	 * }
    + * will result in {@Code Result is 0.26973686 (Float)} + * + * @param args Two arguments that are parsable to numbers. + */ + public static void main(String[] args) { + Double a = Double.parseDouble(args[0]); + Double b = Double.parseDouble(args[1]); + + Calculator mult = new Calculator(); + + Number result = mult.doCalc(a, b); + + System.out.println(mult.toString(result)); + } + + public String toString(Number result) { + return "Result is " + result + " (" + result.getClass().getSimpleName() + ")"; + } + + +} diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-1/src/main/resources/andhow.properties b/andhow-testing/andhow-simulated-app-tests/example-app-1/src/main/resources/andhow.properties new file mode 100644 index 00000000..88c08e6d --- /dev/null +++ b/andhow-testing/andhow-simulated-app-tests/example-app-1/src/main/resources/andhow.properties @@ -0,0 +1,8 @@ +# By default, AndHow will find and load a properties file named +# 'checker.production.properties' if it exists on the classpath. +# +# An additional 'checker.production.properties' file can be placed on the +# test classpath to provide a different configuration during +# testing, as this example project does. + +com.bigcorp.Calculator.MODE: DOUBLE \ No newline at end of file diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-1/src/test/java/com/bigcorp/CalculatorDefaultTest.java b/andhow-testing/andhow-simulated-app-tests/example-app-1/src/test/java/com/bigcorp/CalculatorDefaultTest.java new file mode 100644 index 00000000..82942a70 --- /dev/null +++ b/andhow-testing/andhow-simulated-app-tests/example-app-1/src/test/java/com/bigcorp/CalculatorDefaultTest.java @@ -0,0 +1,48 @@ +package com.bigcorp; + +import org.junit.jupiter.api.Test; + +import static com.github.stefanbirkner.systemlambda.SystemLambda.tapSystemOut; +import static org.junit.jupiter.api.Assertions.*; + +/** + * In the test environment, the application is configured to use the 'FLOAT' + * implementation, as configured in the {@Code andhow.properties} on the TEST CLASSPATH. + * + * Resources on the test classpath override the main classpath, so this is a natural and useful + * outcome. Just like in production, AndHow will auto-discover its configuration during testing. + *

    + * Using the test {@Code andhow.properties} file, however, means that all the tests run with the + * same 'FLOAT' configuration value. How do we test the application with the DOUBLE configuration? + * See {@Code CalculatorDoubleConfigTest} for the answer... + *

    + * ...and much more flexible and complex configuration is possible for production and testing - + * be sure to look at other of the 'simulated-app-tests'. + */ +class CalculatorDefaultTest { + + /** + * Verify that the app runs in FLOAT mode in the test environment. + */ + @Test + public void verifyTestEnvironmentConfiguration() { + Calculator mult = new Calculator(); + + Number result = mult.doCalc(1.23D, 4.56D); + + assertEquals(Float.class, result.getClass(), "The result should be 'Float', as configured"); + assertEquals("FLOAT", Calculator.MODE.getValue()); + + //System.out.println(mult.toString(result)); + } + + @Test + public void mainMethodShouldPrintResultToSystemOut() throws Exception { + + String text = tapSystemOut(() -> { + Calculator.main(new String[] {"4", "2"}); + }); + + assertTrue(text.contains("2.0") && text.contains("Float")); + } +} \ No newline at end of file diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-1/src/test/java/com/bigcorp/CalculatorDoubleConfigTest.java b/andhow-testing/andhow-simulated-app-tests/example-app-1/src/test/java/com/bigcorp/CalculatorDoubleConfigTest.java new file mode 100644 index 00000000..36249a6d --- /dev/null +++ b/andhow-testing/andhow-simulated-app-tests/example-app-1/src/test/java/com/bigcorp/CalculatorDoubleConfigTest.java @@ -0,0 +1,50 @@ +package com.bigcorp; + +import org.junit.jupiter.api.Test; +import org.yarnandtail.andhow.AndHow; +import org.yarnandtail.andhow.junit5.KillAndHowBeforeEachTest; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * {@Code CalculatorDefaultTest} didn't fully test the app because only some code is executed in the + * FLOAT configuration. This test demos testing other configurations. + *

    + * The {@Code @KillAndHowBeforeEachTest} annotation erases the configured state of AndHow before + * each test so each test can specify its own AndHow configuration. If a test doesn't explicitly + * initialize AndHow, AndHow will initialize normally as soon as the first Property value is referenced. + *

    + * When all tests are complete, {@Code KillAndHowBeforeEachTest} resets the AndHow state + * back to what it was at the start of the test. + */ +@KillAndHowBeforeEachTest // <-- Uses the JUnit extension mechanism +class CalculatorDoubleConfigTest { + + /** + * Test the app with the DOUBLE configuration. + */ + @Test + public void testAppInDoubleConfiguration() { + + //Force AndHow to see DOUBLE for the duration of this test + AndHow.findConfig().addFixedValue(Calculator.MODE, "DOUBLE").build(); + + Calculator mult = new Calculator(); + Number result = mult.doCalc(1.23D, 4.56D); + assertEquals(Double.class, result.getClass(), "The result should now be a Double"); + } + + /** + * If the test method doesn't initialize AndHow, AndHow will initialize automatically as soon + * as a Property is accessed and load the default configuration from the test + * {@Code checker.production.properties} file. + */ + @Test + public void revertToDefaultConfigIfTheTestDoesNotInitializeAndHow() { + + Calculator mult = new Calculator(); + Number result = mult.doCalc(1.23D, 4.56D); + assertEquals(Float.class, result.getClass(), "The result should revert to Float"); + } + +} \ No newline at end of file diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-1/src/test/resources/andhow.properties b/andhow-testing/andhow-simulated-app-tests/example-app-1/src/test/resources/andhow.properties new file mode 100644 index 00000000..770dde19 --- /dev/null +++ b/andhow-testing/andhow-simulated-app-tests/example-app-1/src/test/resources/andhow.properties @@ -0,0 +1,4 @@ +# This properties file on the test classpath will override the +# one on the main classpath for testing. + +com.bigcorp.Calculator.MODE: FLOAT \ No newline at end of file diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-2/pom.xml b/andhow-testing/andhow-simulated-app-tests/example-app-2/pom.xml new file mode 100644 index 00000000..340f4411 --- /dev/null +++ b/andhow-testing/andhow-simulated-app-tests/example-app-2/pom.xml @@ -0,0 +1,41 @@ + + + 4.0.0 + + andhow-simulated-app-tests + org.yarnandtail + 0.4.2-SNAPSHOT + + example-app-2 + + + + + org.yarnandtail + andhow + ${project.version} + + + org.yarnandtail + andhow-test-harness + ${project.version} + + + + app + + + maven-assembly-plugin + + + + com.bigcorp.Checker + + + + + + + \ No newline at end of file diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/java/com/bigcorp/AppInitiation.java b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/java/com/bigcorp/AppInitiation.java new file mode 100644 index 00000000..da340d69 --- /dev/null +++ b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/java/com/bigcorp/AppInitiation.java @@ -0,0 +1,20 @@ +package com.bigcorp; + +import org.yarnandtail.andhow.*; +import org.yarnandtail.andhow.property.StrProp; + +public class AppInitiation implements AndHowInit { + + public static final StrProp ANDHOW_CLASSPATH_FILE = StrProp.builder().mustStartWith("/") + .aliasIn("AH_CLASSPATH") /* Make this easier to config by adding an alias */ + .defaultValue("/checker.default.properties") + .description("Path to a file on the classpath. Classpaths must start w/ a slash.").build(); + + @Override + public AndHowConfiguration getConfiguration() { + + return + StdConfig.instance().setClasspathPropFilePath(ANDHOW_CLASSPATH_FILE); + + } +} diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/java/com/bigcorp/Checker.java b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/java/com/bigcorp/Checker.java new file mode 100644 index 00000000..d0459844 --- /dev/null +++ b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/java/com/bigcorp/Checker.java @@ -0,0 +1,82 @@ +package com.bigcorp; + +import org.yarnandtail.andhow.AndHow; +import org.yarnandtail.andhow.property.IntProp; +import org.yarnandtail.andhow.property.StrProp; + +/** + * A hypothetical class that checks if a configured service url is 'live'. + * The url of the external service is configured by AndHow properties. + *

    + * Of course, this isn't a complete application, but its easy to imagine {@Code doCheck} being + * called by an AWS Lambda function, as a command line utility, or as just a library in a larger app. + */ +public class Checker { + + //All the config Properties for this class + // - Its a best practice to group AndHow Properties into an interface. + interface Config { + StrProp PROTOCOL = StrProp.builder().mustBeNonNull(). + mustEqual("http", "https").build(); + StrProp SERVER = StrProp.builder().mustBeNonNull().build(); + IntProp PORT = IntProp.builder().mustBeNonNull(). + mustBeGreaterThanOrEqualTo(80).mustBeLessThanOrEqualTo(8888).build(); + StrProp PATH = StrProp.builder().mustStartWith("/").build(); + } + + /** + * Builds a url using the AndHow Properties. + * @return + */ + public String getServiceUrl() { + return Config.PROTOCOL.getValue() + "://" + Config.SERVER.getValue() + ":" + + Config.PORT.getValue() + Config.PATH.getValue(); + } + + /* + * In the real world, this method would verify the configured url works... + */ +// public boolean checkTheUrl() { +// ... verify the url return a http 200 code or something ... +// } + + /** + * A main method to run this app as it might be in a real environment. + *

    + * In this main method the {@Code AndHow.findConfig()} method is used to append the cmd line + * arguments to the AndHow configuration. This allows Property values to be configured from + * these arguments, in addition to all the other way they could be configured. + *

    > + * Run it from an IDE , or via command line. + *

    + * To run from command line, first use Maven to create a runnable jar. + * Here are the commands, executed from the root of the AndHow project, to build and run the jar:
    + *

    {@Code
    +	 * > mvn clean package -DskipTests -Dmaven.javadoc.skip=true
    +	 * > java -jar andhow-testing/andhow-simulated-app-tests/example-app-2/target/app.jar
    +	 * }
    + * The output of this command will match the config in {@Code checker.default.properties}: + *
    {@Code
    +	 * Service url: https://default.bigcorp.com:80/validate
    +	 * }
    + * + *
    {@Code
    +	 * > java -Dcom.bigcorp.Calculator.CALC_MODE=FLOAT -jar andhow-testing/andhow-simulated-app-tests/example-app-2/target/app.jar 1.23 4.56
    +	 * }
    + * will result in {@Code Result is 0.26973686 (Float)} + * + * @param args Two arguments that are parsable to numbers. + */ + public static void main(String[] args) { + + //Find the AndHow Configuration and add to it the commandline args so they can be used for + //configuration as well. + AndHow.findConfig().setCmdLineArgs(args).build(); //Have to call build here or it doesn't work!! + + Checker v = new Checker(); + System.out.println("Service url: " + v.getServiceUrl()); // <--display the configured url + } + + + +} diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/resources/checker.default.properties b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/resources/checker.default.properties new file mode 100644 index 00000000..84f787b5 --- /dev/null +++ b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/resources/checker.default.properties @@ -0,0 +1,30 @@ +# AndHow knows all the Property metadata, so it can create this file w/ place-holders for values. + +# ########################################################################################## +# Sample properties file generated by AndHow! +# strong.simple.valid.AppConfiguration - https://github.com/eeverman/andhow +# ########################################################################################## + +# ########################################################################################## +# Property Group com.bigcorp.Checker.Config + +# +# PATH (String) +# The property value must start with '/' +com.bigcorp.Checker.Config.PATH = /validate + +# +# PORT (Integer) NON-NULL +# The property value must: +# - be greater than or equal to 80 +# - be less than or equal to 8888 +com.bigcorp.Checker.Config.PORT = 80 + +# +# PROTOCOL (String) NON-NULL +# The property value must be equal to one of '[http, https]' +com.bigcorp.Checker.Config.PROTOCOL = https + +# +# SERVER (String) NON-NULL +com.bigcorp.Checker.Config.SERVER = default.bigcorp.com diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/resources/checker.production.properties b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/resources/checker.production.properties new file mode 100644 index 00000000..4cb1a073 --- /dev/null +++ b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/resources/checker.production.properties @@ -0,0 +1,30 @@ +# AndHow knows all the Property metadata, so it can create this file w/ place-holders for values. + +# ########################################################################################## +# Sample properties file generated by AndHow! +# strong.simple.valid.AppConfiguration - https://github.com/eeverman/andhow +# ########################################################################################## + +# ########################################################################################## +# Property Group com.bigcorp.Checker.Config + +# +# PATH (String) +# The property value must start with '/' +com.bigcorp.Checker.Config.PATH = /validate + +# +# PORT (Integer) NON-NULL +# The property value must: +# - be greater than or equal to 80 +# - be less than or equal to 8888 +com.bigcorp.Checker.Config.PORT = 80 + +# +# PROTOCOL (String) NON-NULL +# The property value must be equal to one of '[http, https]' +com.bigcorp.Checker.Config.PROTOCOL = https + +# +# SERVER (String) NON-NULL +com.bigcorp.Checker.Config.SERVER = prod.bigcorp.com diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/resources/checker.stage.properties b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/resources/checker.stage.properties new file mode 100644 index 00000000..41abd034 --- /dev/null +++ b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/main/resources/checker.stage.properties @@ -0,0 +1,30 @@ +# AndHow knows all the Property metadata, so it can create this file w/ place-holders for values. + +# ########################################################################################## +# Sample properties file generated by AndHow! +# strong.simple.valid.AppConfiguration - https://github.com/eeverman/andhow +# ########################################################################################## + +# ########################################################################################## +# Property Group com.bigcorp.Checker.Config + +# +# PATH (String) +# The property value must start with '/' +com.bigcorp.Checker.Config.PATH = /validate + +# +# PORT (Integer) NON-NULL +# The property value must: +# - be greater than or equal to 80 +# - be less than or equal to 8888 +com.bigcorp.Checker.Config.PORT = 80 + +# +# PROTOCOL (String) NON-NULL +# The property value must be equal to one of '[http, https]' +com.bigcorp.Checker.Config.PROTOCOL = https + +# +# SERVER (String) NON-NULL +com.bigcorp.Checker.Config.SERVER = stage.bigcorp.com diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-2/src/test/java/com/bigcorp/CheckerDefaultAndMainArgsTest.java b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/test/java/com/bigcorp/CheckerDefaultAndMainArgsTest.java new file mode 100644 index 00000000..a8d245d3 --- /dev/null +++ b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/test/java/com/bigcorp/CheckerDefaultAndMainArgsTest.java @@ -0,0 +1,74 @@ +package com.bigcorp; + +import org.junit.jupiter.api.Test; +import org.yarnandtail.andhow.junit5.KillAndHowBeforeThisTest; + +import static com.github.stefanbirkner.systemlambda.SystemLambda.tapSystemOut; +import static org.junit.jupiter.api.Assertions.*; + +public class CheckerDefaultAndMainArgsTest { + + /** + * Verify the the app sees its default configuration. + *

    + * In the {@Code AppInitialtion.ANDHOW_CLASSPATH_FILE} Property, the default value is + * 'checker.default.properties'. In that same class we also tell AndHow to use the value of that + * Property to decide which property file to use, thus property values are read from + * 'checker.default.properties'. + */ + @Test + public void verifyTestEnvironmentConfiguration() { + Checker v = new Checker(); + + String url = v.getServiceUrl(); + + assertEquals("https://default.bigcorp.com:80/validate", url, + "The url should match the configuration in checker.default.properties"); + } + + /** + * Since the main method calls {@Code AndHow.findConfig()...build()}, forcing AndHow to build and + * initialize itself, we must 'kill' the AndHow configured state before calling main. + * Otherwise AndHow would throw a RuntimeException. + *

    + * In production, its AndHow's job to enforce a single, stable configuration state and it complains + * loudly if application code tries to re-initialize it. During testing, however, we need to + * 'break the rules' with things like {@Code @KillAndHowBeforeThisTest} and other AndHow test + * helpers. + * @throws Exception + */ + @KillAndHowBeforeThisTest + @Test + public void mainMethodAlsoSeesDefaultProperties() throws Exception { + + String text = tapSystemOut(() -> { + Checker.main(new String[]{}); + }); + + assertTrue(text.contains("https://default.bigcorp.com:80/validate"), + "The url should match the configuration in checker.default.properties"); + } + + /** + * The main method includes {@Code AndHow.findConfig().setCmdLineArgs(args)...} to allow AndHow + * to process the main args. + *

    + * This test shows how a key=value pair can be passed as an argument to main. Looking at the + * code in {@Code AppInitiation}, the key matches the {@Code ANDHOW_CLASSPATH_FILE} Property we + * told AndHow to use to decide which file to read. To make it easier to specify that Property, + * we added the alias 'AH_CLASSPATH' so we don't need to use the full class name of that Property. + * @throws Exception + */ + @KillAndHowBeforeThisTest + @Test + public void mainMethodWillAcceptConfigForFilePath() throws Exception { + + String text = tapSystemOut(() -> { + //pass the alias name of ANDHOW_CLASSPATH_FILE and a value as a main arg + Checker.main(new String[]{ "AH_CLASSPATH=/checker.test.properties"}); + }); + + assertTrue(text.contains("http://localhost:8888/localValidate"), + "The url should match the configuration in checker.test.properties"); + } +} \ No newline at end of file diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-2/src/test/java/com/bigcorp/CheckerFixedValuesTest.java b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/test/java/com/bigcorp/CheckerFixedValuesTest.java new file mode 100644 index 00000000..d4dc8a6e --- /dev/null +++ b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/test/java/com/bigcorp/CheckerFixedValuesTest.java @@ -0,0 +1,85 @@ +package com.bigcorp; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.yarnandtail.andhow.AndHow; +import org.yarnandtail.andhow.api.AppFatalException; +import org.yarnandtail.andhow.junit5.KillAndHowBeforeAllTests; +import org.yarnandtail.andhow.junit5.KillAndHowBeforeThisTest; + +import static com.github.stefanbirkner.systemlambda.SystemLambda.tapSystemOut; +import static org.junit.jupiter.api.Assertions.*; + +/** + * Lets say there is a very specific configuration state we need to test our app in. + * We could do that with a separate properties file, but another way is to configure AndHow with + * 'fixed values' right in a test. + *

    + * The {@Code @KillAndHowBeforeAllTests} annotation resets AndHow before the start of testing and + * restores it after all tests are complete. The setup method uses + * {@Code AndHow.findConfig()...build()} to initialize AndHow with several 'fixed values' for the + * test scenario. The AndHow state is not modified between tests, so all tests share the same + * AndHow configuration state. + */ +@KillAndHowBeforeAllTests +public class CheckerFixedValuesTest { + + /** + * AndHow was 'killed' at the start of the test class, so this setup method can create a new + * AndHow configuration state for all the tests in this class. When all the tests are complete, + * the original state is restored. + */ + @BeforeAll + public static void setup() { + AndHow.findConfig() + .addFixedValue(Checker.Config.PROTOCOL, "https") + .addFixedValue(Checker.Config.SERVER, "imgs.xkcd.com") + .addFixedValue(Checker.Config.PORT, 80) + .addFixedValue(Checker.Config.PATH, "/comics/the_mother_of_all_suspicious_files.png") + .build(); //build() initializes AndHow, otherwise it would wait for the 1st Property access. + } + + /** + * Verify the the app only sees the 'fixed' values from above. + */ + @Test + public void verifyTestEnvironmentConfiguration() { + Checker v = new Checker(); + + String url = v.getServiceUrl(); + + assertEquals("https://imgs.xkcd.com:80/comics/the_mother_of_all_suspicious_files.png", url, + "The url is build based on the fixed values in the 'BeforeAll' method"); + } + + /** + * All the tests in this class will see the same AndHow configuration... + */ + @Test + public void verifyTestEnvironmentConfiguration2() { + assertEquals("imgs.xkcd.com", Checker.Config.SERVER.getValue()); + } + + /** + * One of AndHow's main jobs is to enforce a single, stable configuration state and complain + * loudly (with an AppFatalException) if application code tries to change that state by + * re-initializing AndHow. This test verifies that. + *

    + * The main() method explicitly initializes AndHow and so does this test class. Thus, the main + * method will throw a {@Code AppFatalException} because AndHow detects the attempt to + * reinitialize it. + *

    + * If we did want to call the main method here, @{Code @KillAndHowBeforeThisTest} could be added + * to this test method to start over with an unconfigured state for just this test. + * @throws Exception + */ + @Test + public void mainMethodShouldThrowAnErrorBecauseAndHowIsAlreadyInitialized() throws Exception { + + assertThrows(AppFatalException.class, () -> + Checker.main(new String[]{}) + ); + + } + +} \ No newline at end of file diff --git a/andhow-testing/andhow-simulated-app-tests/example-app-2/src/test/resources/checker.test.properties b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/test/resources/checker.test.properties new file mode 100644 index 00000000..1fcd74a2 --- /dev/null +++ b/andhow-testing/andhow-simulated-app-tests/example-app-2/src/test/resources/checker.test.properties @@ -0,0 +1,30 @@ +# AndHow knows all the Property metadata, so it can create this file w/ place-holders for values. + +# ########################################################################################## +# Sample properties file generated by AndHow! +# strong.simple.valid.AppConfiguration - https://github.com/eeverman/andhow +# ########################################################################################## + +# ########################################################################################## +# Property Group com.bigcorp.Checker.Config + +# +# PATH (String) +# The property value must start with '/' +com.bigcorp.Checker.Config.PATH = /localValidate + +# +# PORT (Integer) NON-NULL +# The property value must: +# - be greater than or equal to 80 +# - be less than or equal to 8888 +com.bigcorp.Checker.Config.PORT = 8888 + +# +# PROTOCOL (String) NON-NULL +# The property value must be equal to one of '[http, https]' +com.bigcorp.Checker.Config.PROTOCOL = http + +# +# SERVER (String) NON-NULL +com.bigcorp.Checker.Config.SERVER = localhost diff --git a/andhow-testing/andhow-simulated-app-tests/pom.xml b/andhow-testing/andhow-simulated-app-tests/pom.xml index 8f11b49d..55dd0793 100644 --- a/andhow-testing/andhow-simulated-app-tests/pom.xml +++ b/andhow-testing/andhow-simulated-app-tests/pom.xml @@ -4,19 +4,41 @@ org.yarnandtail andhow-parent - 0.4.1-SNAPSHOT + 0.4.2-SNAPSHOT ../../pom.xml andhow-simulated-app-tests pom andhow-multimodule-dataprocess + example-app-1 + example-app-2 + + + + maven-assembly-plugin + + + jar-with-dependencies + + false + + + + package + + single + + + + + + - true maven-deploy-plugin true diff --git a/andhow-testing/andhow-system-tests/pom.xml b/andhow-testing/andhow-system-tests/pom.xml index 21718d43..f4740f2a 100644 --- a/andhow-testing/andhow-system-tests/pom.xml +++ b/andhow-testing/andhow-system-tests/pom.xml @@ -3,7 +3,7 @@ org.yarnandtail andhow-parent - 0.4.1-SNAPSHOT + 0.4.2-SNAPSHOT ../../pom.xml @@ -20,7 +20,7 @@ ${project.groupId} andhow ${project.version} - provided + test ${project.groupId} @@ -28,12 +28,6 @@ ${project.version} test - - - junit - junit - test - commons-io commons-io diff --git a/andhow-testing/andhow-system-tests/src/test/java/org/yarnandtail/andhow/AndHowTest.java b/andhow-testing/andhow-system-tests/src/test/java/org/yarnandtail/andhow/AndHowTest.java index d50e097f..bc57e865 100644 --- a/andhow-testing/andhow-system-tests/src/test/java/org/yarnandtail/andhow/AndHowTest.java +++ b/andhow-testing/andhow-system-tests/src/test/java/org/yarnandtail/andhow/AndHowTest.java @@ -1,11 +1,13 @@ package org.yarnandtail.andhow; import java.lang.reflect.Field; -import org.junit.*; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.AfterEach; +import static org.junit.jupiter.api.Assertions.*; import org.yarnandtail.andhow.name.CaseInsensitiveNaming; import org.yarnandtail.andhow.property.StrProp; -import static org.junit.Assert.*; /** * @@ -24,12 +26,12 @@ public class AndHowTest { private AndHow originalAndHowInstance; - @Before + @BeforeEach public void clearAndHow() { originalAndHowInstance = setAndHowInstance(null); } - @After + @AfterEach public void restoreAndHow() { setAndHowInstance(originalAndHowInstance); } @@ -45,8 +47,8 @@ public void testFindConfig() { AndHowConfiguration config1 = AndHow.findConfig(); AndHowConfiguration config2 = AndHow.findConfig(); - assertNotEquals("Should return a new instance each time", config1, config2); - assertFalse("findConfig should not force initialization", AndHow.isInitialize()); + assertNotEquals(config1, config2, "Should return a new instance each time"); + assertFalse(AndHow.isInitialize(), "findConfig should not force initialization"); } /** diff --git a/andhow-testing/andhow-test-harness/pom.xml b/andhow-testing/andhow-test-harness/pom.xml index 2944339f..b921d495 100644 --- a/andhow-testing/andhow-test-harness/pom.xml +++ b/andhow-testing/andhow-test-harness/pom.xml @@ -3,7 +3,7 @@ org.yarnandtail andhow-parent - 0.4.1-SNAPSHOT + 0.4.2-SNAPSHOT ../../pom.xml @@ -19,18 +19,49 @@ ${project.groupId} andhow-core ${project.version} - provided + compile + true - + + + + org.springframework + spring-test + compile + + + junit junit - provided + compile + true - org.springframework - spring-test + org.junit.jupiter + junit-jupiter-api + compile + true + + + org.junit.jupiter + junit-jupiter-engine + compile + true + + + org.junit.vintage + junit-vintage-engine compile + true diff --git a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowJunit4TestBase.java b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowJunit4TestBase.java new file mode 100644 index 00000000..9b8d711c --- /dev/null +++ b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowJunit4TestBase.java @@ -0,0 +1,69 @@ +package org.yarnandtail.andhow; + +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; + + +/** + * Optional class that can be used by app developers as a base class for JUnit 4 tests. + * + * This class resets AndHow and System.properties to their prior + * state after each test run, as well as after all tests have run. This, used with + * NonProductionConfig.instance().forceBuild() + * and setting SystemProperties at the start of a test, can be used to set a + * specific AndHow configuration for a test or suite of tests. + * + * + * Here is a exampleHere is a example that shows how this can be used. + * + * @author eeverman + */ +public class AndHowJunit4TestBase extends AndHowTestBaseImpl { + + /** + * Stores the AndHow Core (its state) and System Properties prior to a test class. + * It also sets the logging level for SimpleNamingContextBuilder (a JNDI + * related class) to SEVERE. If JNDI is used for a test, it's startup + * is verbose to System.out, so this turns it off. + */ + @BeforeClass + public static void andHowSnapshotBeforeTestClass() { + AndHowTestBaseImpl.andHowSnapshotBeforeTestClass(); + } + + /** + * Restores the AndHow Core (its state) and System Properties + * that were previously stored prior to this class' test run. + * It also resets the logging level for SimpleNamingContextBuilder (a JNDI + * related class) to what ever it was prior to the run. + */ + @AfterClass + public static void resetAndHowSnapshotAfterTestClass() { + AndHowTestBaseImpl.resetAndHowSnapshotAfterTestClass(); + } + + /** + * Stores the AndHow Core (its state) and System Properties prior to a test. + * It also sets the logging level for SimpleNamingContextBuilder (a JNDI + * related class) to SEVERE. If JNDI is used for a test, it's startup + * is verbose to System.out, so this turns it off. + */ + @Before + public void andHowSnapshotBeforeSingleTest() { + super.andHowSnapshotBeforeSingleTest(); + } + + /** + * Restores the AndHow Core (its state) and System Properties + * that were previously stored prior to a test run. + * It also resets the logging level for SimpleNamingContextBuilder (a JNDI + * related class) to what ever it was prior to the run. + */ + @After + public void resetAndHowSnapshotAfterSingleTest() { + super.resetAndHowSnapshotAfterSingleTest(); + } + +} diff --git a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowJunit5TestBase.java b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowJunit5TestBase.java new file mode 100644 index 00000000..59d316f7 --- /dev/null +++ b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowJunit5TestBase.java @@ -0,0 +1,69 @@ +package org.yarnandtail.andhow; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.BeforeAll; + + +/** + * Optional class that can be used by app developers as a base class for JUnit 4 tests. + * + * This class resets AndHow and System.properties to their prior + * state after each test run, as well as after all tests have run. This, used with + * NonProductionConfig.instance().forceBuild() + * and setting SystemProperties at the start of a test, can be used to set a + * specific AndHow configuration for a test or suite of tests. + * + * + * Here is a exampleHere is a example that shows how this can be used. + * + * @author eeverman + */ +public class AndHowJunit5TestBase extends AndHowTestBaseImpl { + + /** + * Stores the AndHow Core (its state) and System Properties prior to a test class. + * It also sets the logging level for SimpleNamingContextBuilder (a JNDI + * related class) to SEVERE. If JNDI is used for a test, it's startup + * is verbose to System.out, so this turns it off. + */ + @BeforeAll + public static void andHowSnapshotBeforeTestClass() { + AndHowTestBaseImpl.andHowSnapshotBeforeTestClass(); + } + + /** + * Restores the AndHow Core (its state) and System Properties + * that were previously stored prior to this class' test run. + * It also resets the logging level for SimpleNamingContextBuilder (a JNDI + * related class) to what ever it was prior to the run. + */ + @AfterAll + public static void resetAndHowSnapshotAfterTestClass() { + AndHowTestBaseImpl.resetAndHowSnapshotAfterTestClass(); + } + + /** + * Stores the AndHow Core (its state) and System Properties prior to a test. + * It also sets the logging level for SimpleNamingContextBuilder (a JNDI + * related class) to SEVERE. If JNDI is used for a test, it's startup + * is verbose to System.out, so this turns it off. + */ + @BeforeEach + public void andHowSnapshotBeforeSingleTest() { + super.andHowSnapshotBeforeSingleTest(); + } + + /** + * Restores the AndHow Core (its state) and System Properties + * that were previously stored prior to a test run. + * It also resets the logging level for SimpleNamingContextBuilder (a JNDI + * related class) to what ever it was prior to the run. + */ + @AfterEach + public void resetAndHowSnapshotAfterSingleTest() { + super.resetAndHowSnapshotAfterSingleTest(); + } + +} diff --git a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowNonProductionUtil.java b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowNonProductionUtil.java index 87ed5875..00fe900d 100644 --- a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowNonProductionUtil.java +++ b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowNonProductionUtil.java @@ -59,7 +59,7 @@ public static AndHowCore getAndHowCore() { /** * Sets a new {@code AndHowCore} * - * This inserts an entire new state into AndHow. Inserting a {@null} core + * This inserts an entire new state into AndHow. Inserting a null core * puts AndHow into a reset state that is invalid during production, but * can be useful during testing. In this state, AndHow will allow itself * to be reinitialized, which is not the intended operation during diff --git a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowTestBase.java b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowTestBase.java index 4e9640eb..b4cc0e76 100644 --- a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowTestBase.java +++ b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowTestBase.java @@ -1,106 +1,67 @@ package org.yarnandtail.andhow; -import java.util.Properties; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.naming.NamingException; import org.junit.*; -import org.springframework.mock.jndi.SimpleNamingContextBuilder; -import org.yarnandtail.andhow.internal.AndHowCore; + /** - * All tests using AppConfig must extend this class so they have access to the - * one and only AppConfig.Reloader, which is a single backdoor to cause the - * AppConfig to reload. + * Optional class that can be used by app developers as a base class for JUnit 4 tests. + * + * This class resets AndHow and System.properties to their prior + * state after each test run, as well as after all tests have run. This, used with + * NonProductionConfig.instance().forceBuild() + * and setting SystemProperties at the start of a test, can be used to set a + * specific AndHow configuration for a test or suite of tests. + * + * + * Here is a exampleHere is a example that shows how this can be used. * * @author eeverman + * @depricated Use AndHowJunit4TestBase instead, or upgrade to AndHowJunit5TestBase. */ -public class AndHowTestBase { - - private static AndHowCore beforeClassCore; - - private AndHowCore beforeTestCore; - - private static Level beforeClassLogLevel; - - /** - * System properties before the tests and subclass @BeforeClass initializer - * are run. Properties prior to the initialization of this test class are - * stored and then reinstated after the test class is complete. - */ - private static Properties beforeClassSystemProps; - - /** - * System properties before an individual test is run and the subclass @Before - * initializers are run. Properties prior to a test are - * stored and then reinstated after the test is complete. If a Test classes - * uses a @BeforeClass initializer that sets system properties, this will - * reinstate to that state. - */ - private Properties beforeTestSystemProps; - - /** - * Builder for a temporary JNDI context - */ - private static SimpleNamingContextBuilder builder; - - - +public class AndHowTestBase extends AndHowTestBaseImpl { + /** - * Simple consistent way to get an empty JNDI context. - * - * bind() each variable, then call build(). - * - * @return - * @throws NamingException + * Stores the AndHow Core (its state) and System Properties prior to a test class. + * It also sets the logging level for SimpleNamingContextBuilder (a JNDI + * related class) to SEVERE. If JNDI is used for a test, it's startup + * is verbose to System.out, so this turns it off. */ - public SimpleNamingContextBuilder getJndi() throws NamingException { - if (builder == null) { - builder = SimpleNamingContextBuilder.emptyActivatedContextBuilder(); - } - return builder; - } - @BeforeClass - public static void andHowSnapshotBeforeTestClass() throws Exception { - //The SimpleNamingContextBuilder uses Commons Logging, which defaults to - //using Java logging. It spews a bunch of stuff the console during tests, - //so this turns that off. - - beforeClassLogLevel = Logger.getGlobal().getLevel(); //store log level before class - Logger.getGlobal().setLevel(Level.SEVERE); - Logger.getLogger(SimpleNamingContextBuilder.class.getCanonicalName()).setLevel(Level.SEVERE); - - - beforeClassCore = AndHowNonProductionUtil.getAndHowCore(); - beforeClassSystemProps = AndHowNonProductionUtil.clone(System.getProperties()); + public static void andHowSnapshotBeforeTestClass() { + AndHowTestBaseImpl.andHowSnapshotBeforeTestClass(); } - + + /** + * Restores the AndHow Core (its state) and System Properties + * that were previously stored prior to this class' test run. + * It also resets the logging level for SimpleNamingContextBuilder (a JNDI + * related class) to what ever it was prior to the run. + */ @AfterClass public static void resetAndHowSnapshotAfterTestClass() { - System.setProperties(beforeClassSystemProps); - AndHowNonProductionUtil.setAndHowCore(beforeClassCore); - - //Reset to the log level prior to the test class - Logger.getGlobal().setLevel(beforeClassLogLevel); - Logger.getLogger(SimpleNamingContextBuilder.class.getCanonicalName()).setLevel(beforeClassLogLevel); + AndHowTestBaseImpl.resetAndHowSnapshotAfterTestClass(); } - + + /** + * Stores the AndHow Core (its state) and System Properties prior to a test. + * It also sets the logging level for SimpleNamingContextBuilder (a JNDI + * related class) to SEVERE. If JNDI is used for a test, it's startup + * is verbose to System.out, so this turns it off. + */ @Before public void andHowSnapshotBeforeSingleTest() { - beforeTestSystemProps = AndHowNonProductionUtil.clone(System.getProperties()); - beforeTestCore = AndHowNonProductionUtil.getAndHowCore(); + super.andHowSnapshotBeforeSingleTest(); } - + + /** + * Restores the AndHow Core (its state) and System Properties + * that were previously stored prior to a test run. + * It also resets the logging level for SimpleNamingContextBuilder (a JNDI + * related class) to what ever it was prior to the run. + */ @After public void resetAndHowSnapshotAfterSingleTest() { - System.setProperties(beforeTestSystemProps); - AndHowNonProductionUtil.setAndHowCore(beforeTestCore); - - if (builder != null) { - builder.clear(); - } + super.resetAndHowSnapshotAfterSingleTest(); } - - + } diff --git a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowTestBaseImpl.java b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowTestBaseImpl.java new file mode 100644 index 00000000..42ba03e1 --- /dev/null +++ b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/AndHowTestBaseImpl.java @@ -0,0 +1,133 @@ +package org.yarnandtail.andhow; + +import org.springframework.mock.jndi.SimpleNamingContextBuilder; +import org.yarnandtail.andhow.internal.AndHowCore; + +import javax.naming.NamingException; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Base class for AndHowJunit4/5TestBase classes. + * See those classes for documentation. + */ +public class AndHowTestBaseImpl { + private static AndHowCore beforeClassCore; + private static Level beforeClassLogLevel; + /** + * System properties before the tests and subclass @BeforeClass initializer + * are run. Properties prior to the initialization of this test class are + * stored and then reinstated after the test class is complete. + */ + private static Properties beforeClassSystemProps; + /** + * Builder for a temporary JNDI context + */ + private static SimpleNamingContextBuilder builder; + private AndHowCore beforeTestCore; + /** + * System properties before an individual test is run and the subclass @Before + * initializers are run. Properties prior to a test are + * stored and then reinstated after the test is complete. If a Test classes + * uses a @BeforeClass initializer that sets system properties, this will + * reinstate to that state. + */ + private Properties beforeTestSystemProps; + + /** + * Stores the AndHow Core (its state) and System Properties prior to a test class. + * It also sets the logging level for SimpleNamingContextBuilder (a JNDI + * related class) to SEVERE. If JNDI is used for a test, it's startup + * is verbose to System.out, so this turns it off. + */ + public static void andHowSnapshotBeforeTestClass() { + //The SimpleNamingContextBuilder uses Commons Logging, which defaults to + //using Java logging. It spews a bunch of stuff the console during tests, + //so this turns that off. + + beforeClassLogLevel = Logger.getGlobal().getLevel(); //store log level before class + Logger.getGlobal().setLevel(Level.SEVERE); + Logger.getLogger(SimpleNamingContextBuilder.class.getCanonicalName()).setLevel(Level.SEVERE); + + + beforeClassCore = AndHowNonProductionUtil.getAndHowCore(); + beforeClassSystemProps = AndHowNonProductionUtil.clone(System.getProperties()); + } + + /** + * Restores the AndHow Core (its state) and System Properties + * that were previously stored prior to this class' test run. + * It also resets the logging level for SimpleNamingContextBuilder (a JNDI + * related class) to what ever it was prior to the run. + * Any JNDI bindings set via getJndi() are cleared. + */ + public static void resetAndHowSnapshotAfterTestClass() { + System.setProperties(beforeClassSystemProps); + AndHowNonProductionUtil.setAndHowCore(beforeClassCore); + + //Reset to the log level prior to the test class + Logger.getGlobal().setLevel(beforeClassLogLevel); + Logger.getLogger(SimpleNamingContextBuilder.class.getCanonicalName()).setLevel(beforeClassLogLevel); + + if (builder != null) { + builder.clear(); + builder.deactivate(); + } + } + + /** + * Simple consistent way to get an empty JNDI context. + *

    + * Call {@code SimpleNamingContextBuilder.bind()} for each variable to add + * to the context, then {@code SimpleNamingContextBuilder.activate()} to + * make the context active. To fetch values from the context, + * use: + *

    {@code
    +	 * InitialContext ctx = new InitialContext();
    +	 * ctx.lookup("java:comp/some/name");
    +	 * }
    + * The context is deactivated and cleared after each test and after the + * test class completes. + * + * @deprecated This will be removed in the next major release to avoid + * having JNDI dependencies in a user visible class. Most user will not + * need to test their apps with JNDI. + * @return A JNDI context for setting properties via JNDI. + * @throws NamingException If JNDI cannot be initiated. + */ + public SimpleNamingContextBuilder getJndi() throws NamingException { + if (builder == null) { + builder = SimpleNamingContextBuilder.emptyActivatedContextBuilder(); + } + return builder; + } + + /** + * Stores the AndHow Core (its state) and System Properties prior to a test. + * It also sets the logging level for SimpleNamingContextBuilder (a JNDI + * related class) to SEVERE. If JNDI is used for a test, it's startup + * is verbose to System.out, so this turns it off. + */ + public void andHowSnapshotBeforeSingleTest() { + beforeTestSystemProps = AndHowNonProductionUtil.clone(System.getProperties()); + beforeTestCore = AndHowNonProductionUtil.getAndHowCore(); + } + + /** + * Restores the AndHow Core (its state) and System Properties + * that were previously stored prior to a test run. + * It also resets the logging level for SimpleNamingContextBuilder (a JNDI + * related class) to what ever it was prior to the run. + * Any JNDI bindings set via getJndi() are cleared. + */ + public void resetAndHowSnapshotAfterSingleTest() { + System.setProperties(beforeTestSystemProps); + AndHowNonProductionUtil.setAndHowCore(beforeTestCore); + + if (builder != null) { + builder.clear(); + builder.deactivate(); + } + } +} diff --git a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeAllTests.java b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeAllTests.java new file mode 100644 index 00000000..b8b6c00a --- /dev/null +++ b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeAllTests.java @@ -0,0 +1,45 @@ +package org.yarnandtail.andhow.junit5; + +import org.junit.jupiter.api.extension.ExtendWith; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Annotation that can be placed on a test class to reset AndHow to its unconfigured state + * (only) before the first test runs. When all tests in the class are done, the + * original AndHow configured state is restored, which may be unconfigured. + *

    + * Example usage: + *

    {@Code
    + * @KillAndHowBeforeAllTests
    + * public class MyJunit5TestClass {
    + *
    + *   @BeforeAll
    + *   public static void configAndHowForAllTests(){
    + * 		AndHow.findConfig()
    + * 				.addFixedValue([AndHowProperty reference or name], [Value for that Property])
    + * 				.addFixedValue(...)
    + * 				.build();
    + *    }
    + *
    + *   ...tests that will all share the same configuration...
    + *
    + * }
    + * }
    + *

    + * Note: Using this annotation on a JUnit test class is the same as using + * {@Code @ExtendWith(KillAndHowBeforeAllTestsExtension.class)} on a class, but this annotation is + * safer because it cannot be put on a method. '@ExtendWith' allows placement on a method, + * but the extension will only work properly on a class. + */ +@Target({ TYPE, ANNOTATION_TYPE }) +@Retention(RUNTIME) +@ExtendWith(KillAndHowBeforeAllTestsExtension.class) +public @interface KillAndHowBeforeAllTests { + +} diff --git a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeAllTestsExtension.java b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeAllTestsExtension.java new file mode 100644 index 00000000..b22ca81a --- /dev/null +++ b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeAllTestsExtension.java @@ -0,0 +1,70 @@ +package org.yarnandtail.andhow.junit5; + +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.yarnandtail.andhow.AndHowNonProductionUtil; +import org.yarnandtail.andhow.internal.AndHowCore; + +/** + * JUnit Extension that can be placed on a test class to reset AndHow to its unconfigured + * state (only) before the first test runs. When all tests in the class are done, the + * original AndHow configured state is restored, which may be unconfigured. + *

    + * With this extension, all tests in the class share the same AndHow configuration, which + * can be set in a {@Code @BeforeAll} setup method. + *

    + * It is easier and safer to use the @{Code @KillAndHowBeforeAllTests} annotation. + * That annotation uses this class and prevents this extension from being used on a test method + * (this extension only works correctly on a test class). + *

    + * Usage example: + *

    {@Code
    + * @ExtendWith(KillAndHowBeforeAllTestsExtension.class)
    + * public class MyJunit5TestClass {
    + *
    + *   @BeforeAll
    + *   public static void configAndHowForAllTests(){
    + * 		AndHow.findConfig()
    + * 				.addFixedValue([AndHowProperty reference or name], [Value for that Property])
    + * 				.addFixedValue(...)
    + * 				.build();
    + *    }
    + *
    + *   ...tests that will all share the same configuration...
    + *
    + * }
    + * }
    + */ +public class KillAndHowBeforeAllTestsExtension implements BeforeAllCallback, AfterAllCallback { + + protected static final String CORE_KEY = "core_key"; + + /** + * Store the state of AndHow before any test is run, then destroy the state + * so AndHow is unconfigured. + * @param extensionContext + * @throws Exception + */ + @Override + public void beforeAll(ExtensionContext extensionContext) throws Exception { + AndHowCore core = AndHowNonProductionUtil.getAndHowCore(); + getStore(extensionContext).put(CORE_KEY, core); + AndHowNonProductionUtil.destroyAndHowCore(); + } + + @Override + public void afterAll(ExtensionContext extensionContext) throws Exception { + AndHowCore core = getStore(extensionContext).remove(CORE_KEY, AndHowCore.class); + AndHowNonProductionUtil.setAndHowCore(core); + } + + /** + * Create or return the unique storage space for this test class for this extension. + * @param context + * @return + */ + protected ExtensionContext.Store getStore(ExtensionContext context) { + return context.getStore(ExtensionContext.Namespace.create(getClass(), context.getRequiredTestClass())); + } +} diff --git a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeEachTest.java b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeEachTest.java new file mode 100644 index 00000000..3d11463d --- /dev/null +++ b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeEachTest.java @@ -0,0 +1,48 @@ +package org.yarnandtail.andhow.junit5; + +import org.junit.jupiter.api.extension.ExtendWith; +import org.yarnandtail.andhow.junit5.KillAndHowBeforeEachTestExtension; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.TYPE; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Annotation that can be used on a JUnit test class to reset AndHow to its unconfigured state + * prior to each individual test. When all tests in the class are done, the original + * configured state of AndHow is restored, which may be unconfigured. + *

    + * Example usage: + *

    {@Code
    + * @KillAndHowBeforeEachTest
    + * public class MyJunit5TestClass {
    + *
    + *   @Test
    + *   public void doATest(){
    + * 		AndHow.findConfig()
    + * 				.addFixedValue([AndHowProperty reference or name], [Value for that Property])
    + * 				.addFixedValue(...)
    + * 				.build();
    + *
    + * 		  ...	code for this test...
    + *    }
    + *
    + *   ...other tests that can each configure AndHow...
    + *
    + * }
    + * }
    + *

    + * Note: Using this annotation on a JUnit test class is the same as using + * {@Code @ExtendWith(KillAndHowBeforeEachTestExtension.class)} on a class, but this annotation is + * safer because it cannot be put on a method. '@ExtendWith' allows placement on a method, + * but the extension will only work properly on a class. + */ +@Target({ TYPE, ANNOTATION_TYPE }) +@Retention(RUNTIME) +@ExtendWith(KillAndHowBeforeEachTestExtension.class) +public @interface KillAndHowBeforeEachTest { + +} diff --git a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeEachTestExtension.java b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeEachTestExtension.java new file mode 100644 index 00000000..4c034b22 --- /dev/null +++ b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeEachTestExtension.java @@ -0,0 +1,50 @@ +package org.yarnandtail.andhow.junit5; + +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.yarnandtail.andhow.AndHowNonProductionUtil; + + +/** + * JUnit Extension that can be placed on a test class to reset AndHow to its + * unconfigured state before each test runs. When all tests in the class are done, the + * original AndHow configured state is restored, which may be unconfigured. + *

    + * It is easier and safer to use the @{Code @KillAndHowBeforeEachTest} annotation. + * That annotation uses this class and prevents this extension from being used on a test method + * (this extension only works correctly on a test class). + *

    + * Usage example: + *

    {@Code
    + * @ExtendWith(KillAndHowBeforeEachTestExtension.class)
    + * public class MyJunit5TestClass {
    + *
    + *   @Test
    + *   public void doATest(){
    + * 		AndHow.findConfig()
    + * 				.addFixedValue([AndHowProperty reference or name], [Value for that Property])
    + * 				.addFixedValue(...)
    + * 				.build();
    + *
    + * 		  ...	code for this test...
    + *    }
    + *
    + *   ...other tests that can each configure AndHow...
    + *
    + * }
    + * }
    + */ +public class KillAndHowBeforeEachTestExtension extends KillAndHowBeforeAllTestsExtension + implements BeforeEachCallback { + + /** + * Destroy the AndHow state before each test so that each starts with AndHow unconfigured. + * @param extensionContext + * @throws Exception + */ + @Override + public void beforeEach(ExtensionContext extensionContext) throws Exception { + AndHowNonProductionUtil.destroyAndHowCore(); + } + +} diff --git a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeThisTest.java b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeThisTest.java new file mode 100644 index 00000000..c3ba7260 --- /dev/null +++ b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeThisTest.java @@ -0,0 +1,38 @@ +package org.yarnandtail.andhow.junit5; + +import org.junit.jupiter.api.extension.ExtendWith; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.ANNOTATION_TYPE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * Annotation that can be placed on an individual test method to reset AndHow to its + * unconfigured state before the test runs. When the test is done, the original AndHow configured + * state is restored, which may be unconfigured. + *

    + * Example usage: + *

    {@Code
    + * public class MyJunit5TestClass {
    + *
    + *   @Test
    + *   @KillAndHowBeforeThisTest
    + *   public void aTest() { .... }
    + *
    + * }
    + * }
    + *

    + * Note: Using this annotation on a JUnit test method is the same as using + * {@Code @ExtendWith(KillAndHowBeforeThisTestExtension.class)} on a method, but this annotation is + * safer because it cannot be put on a class. '@ExtendWith' allows placement on a class, + * but the extension will only work properly on a method. + */ +@Target({ METHOD, ANNOTATION_TYPE }) +@Retention(RUNTIME) +@ExtendWith(KillAndHowBeforeThisTestExtension.class) +public @interface KillAndHowBeforeThisTest { + +} diff --git a/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeThisTestExtension.java b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeThisTestExtension.java new file mode 100644 index 00000000..0dbabc5e --- /dev/null +++ b/andhow-testing/andhow-test-harness/src/main/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeThisTestExtension.java @@ -0,0 +1,72 @@ +package org.yarnandtail.andhow.junit5; + +import org.junit.jupiter.api.extension.*; +import org.yarnandtail.andhow.AndHowNonProductionUtil; +import org.yarnandtail.andhow.internal.AndHowCore; + +import java.lang.reflect.Method; +import java.util.Optional; + +/** + * JUnit Extension that can be placed on an individual test method to reset AndHow to its + * unconfigured state before the test runs. When the test is done, the original AndHow configured + * state is restored, which may be unconfigured. + *

    + * It is easier and safer to use the @{Code @KillAndHowBeforeThisTest} annotation. + * That annotation uses this class and prevents this extension from being used on a test class + * (this extension only works correctly on a test method). + *

    + * Usage example: + *

    {@Code
    + * public class MyJunit5TestClass {
    + *
    + *   @Test
    + *   @ExtendWith(KillAndHowBeforeThisTestExtension.class)
    + *   public void aTest() { .... }
    + *
    + * }
    + * }
    + */ +public class KillAndHowBeforeThisTestExtension implements BeforeEachCallback, AfterEachCallback { + + public static final String CORE_KEY = "core_key"; + + /** + * The state of AndHow, stored before any tests in the current test class are run. + */ + private AndHowCore beforeAllAndHowCore; + + /** + * Store the state of AndHow before this test, then destroy the state so AndHow is unconfigured. + * @param extensionContext + * @throws Exception + */ + @Override + public void beforeEach(ExtensionContext extensionContext) throws Exception { + AndHowCore core = AndHowNonProductionUtil.getAndHowCore(); + getStore(extensionContext).put(CORE_KEY, core); + AndHowNonProductionUtil.destroyAndHowCore(); + } + + /** + * Restore the state of AndHow to what it was before this test. + * @param context + * @throws Exception + */ + @Override + public void afterEach(ExtensionContext context) throws Exception { + AndHowCore core = getStore(context).remove(CORE_KEY, AndHowCore.class); + AndHowNonProductionUtil.setAndHowCore(core); + } + + /** + * Create or return the unique storage space for this test class for this extension. + * @param context + * @return + */ + private ExtensionContext.Store getStore(ExtensionContext context) { + return context.getStore(ExtensionContext.Namespace.create(getClass(), context.getRequiredTestInstance(), context.getRequiredTestMethod())); + } + + +} diff --git a/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/AndHowJunit4TestBaseTest.java b/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/AndHowJunit4TestBaseTest.java new file mode 100644 index 00000000..40f771a0 --- /dev/null +++ b/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/AndHowJunit4TestBaseTest.java @@ -0,0 +1,66 @@ +package org.yarnandtail.andhow; + +import org.junit.jupiter.api.Test; + +import java.util.Properties; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * The implementation is all in the base class, so here tests are minimal + * to make sure the base class is being called. + */ +class AndHowJunit4TestBaseTest { + + static final String BOB = "bob"; + + @Test + void andHowSnapshotBeforeAndAfterTestClass() { + + Properties originalProperties = System.getProperties(); + + try { + + //We shouldn't have this property before we start + assertNull(System.getProperty(BOB)); + + //Take the snapshot + AndHowJunit4TestBase.andHowSnapshotBeforeTestClass(); + + //Sys props - completely mess them up - reset should reset them... + System.setProperties(new Properties()); //zap all properties + System.setProperty(BOB, BOB); + + + //Are the sysProps messed up just like we did above? + assertEquals(BOB, System.getProperty(BOB)); + assertEquals(1, System.getProperties().size()); + + // + //reset + AndHowJunit4TestBase.resetAndHowSnapshotAfterTestClass(); + + + //Verify we have the exact same set of SysProps after the reset + assertEquals(originalProperties.size(), System.getProperties().size()); + assertTrue(originalProperties.entrySet().containsAll(System.getProperties().entrySet())); + + } finally { + System.setProperties(originalProperties); + } + + } + + + @Test + void andHowSnapshotBeforeAndAfterSingleTest() { + + AndHowJunit4TestBase testBase = new AndHowJunit4TestBase(); + AndHowTestBaseImplTest implTest = new AndHowTestBaseImplTest(); + + implTest.doAndHowSnapshotBeforeAndAfterSingleTest(testBase); + + } + + +} \ No newline at end of file diff --git a/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/AndHowJunit5TestBaseTest.java b/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/AndHowJunit5TestBaseTest.java new file mode 100644 index 00000000..87ed17df --- /dev/null +++ b/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/AndHowJunit5TestBaseTest.java @@ -0,0 +1,66 @@ +package org.yarnandtail.andhow; + +import org.junit.jupiter.api.Test; + +import java.util.Properties; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * The implementation is all in the base class, so here tests are minimal + * to make sure the base class is being called. + */ +class AndHowJunit5TestBaseTest { + + static final String BOB = "bob"; + + @Test + void andHowSnapshotBeforeAndAfterTestClass() { + + Properties originalProperties = System.getProperties(); + + try { + + //We shouldn't have this property before we start + assertNull(System.getProperty(BOB)); + + //Take the snapshot + AndHowJunit5TestBase.andHowSnapshotBeforeTestClass(); + + //Sys props - completely mess them up - reset should reset them... + System.setProperties(new Properties()); //zap all properties + System.setProperty(BOB, BOB); + + + //Are the sysProps messed up just like we did above? + assertEquals(BOB, System.getProperty(BOB)); + assertEquals(1, System.getProperties().size()); + + // + //reset + AndHowJunit5TestBase.resetAndHowSnapshotAfterTestClass(); + + + //Verify we have the exact same set of SysProps after the reset + assertEquals(originalProperties.size(), System.getProperties().size()); + assertTrue(originalProperties.entrySet().containsAll(System.getProperties().entrySet())); + + } finally { + System.setProperties(originalProperties); + } + + } + + + @Test + void andHowSnapshotBeforeAndAfterSingleTest() { + + AndHowJunit5TestBase testBase = new AndHowJunit5TestBase(); + AndHowTestBaseImplTest implTest = new AndHowTestBaseImplTest(); + + implTest.doAndHowSnapshotBeforeAndAfterSingleTest(testBase); + + } + + +} \ No newline at end of file diff --git a/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/AndHowNonProductionUtilTest.java b/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/AndHowNonProductionUtilTest.java index 4a48bc58..3b2c43ff 100644 --- a/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/AndHowNonProductionUtilTest.java +++ b/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/AndHowNonProductionUtilTest.java @@ -3,10 +3,10 @@ package org.yarnandtail.andhow; import java.util.Properties; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.internal.AndHowCore; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; /** * @@ -62,7 +62,7 @@ public void testSetAndHowCore() { AndHowNonProductionUtil.setAndHowCore(ahCore1); //swap cores //The key assertion - assertTrue("Should have inserted a new core", ahCore1 == AndHowNonProductionUtil.getAndHowCore()); + assertTrue(ahCore1 == AndHowNonProductionUtil.getAndHowCore(), "Should have inserted a new core"); } /** @@ -75,12 +75,12 @@ public void testForceRebuild() { AndHowConfiguration config = AndHow.findConfig(); AndHowNonProductionUtil.forceRebuild(config); //Should be OK even when a new build AndHowCore ahCore1 = AndHowNonProductionUtil.getAndHowCore(); - assertNotNull("Util should create a new instance even if no current instance", ahCore1); + assertNotNull(ahCore1, "Util should create a new instance even if no current instance"); AndHowNonProductionUtil.forceRebuild(config); //Now an actual rebuild AndHowCore ahCore2 = AndHowNonProductionUtil.getAndHowCore(); assertNotNull(ahCore2); - assertFalse("The core instances should be different instances", ahCore1 == ahCore2); + assertFalse(ahCore1 == ahCore2, "The core instances should be different instances"); } /** @@ -106,7 +106,7 @@ public void testClone() { @Test public void testDestroyAndHowCore() { - assertNull("AndHow should be null at test start", AndHowNonProductionUtil.getAndHowInstance()); + assertNull(AndHowNonProductionUtil.getAndHowInstance(), "AndHow should be null at test start"); AndHow.instance(); //force creation AndHowNonProductionUtil.destroyAndHowCore(); assertNotNull(AndHowNonProductionUtil.getAndHowInstance()); diff --git a/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/AndHowTestBaseImplTest.java b/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/AndHowTestBaseImplTest.java new file mode 100644 index 00000000..421fbb3c --- /dev/null +++ b/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/AndHowTestBaseImplTest.java @@ -0,0 +1,211 @@ +package org.yarnandtail.andhow; + +import org.junit.jupiter.api.Test; +import org.springframework.mock.jndi.SimpleNamingContextBuilder; +import org.yarnandtail.andhow.internal.AndHowCore; + +import javax.naming.InitialContext; +import javax.naming.NamingException; +import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.*; + +class AndHowTestBaseImplTest { + + static final String BOB_NAME = SimpleConfig.class.getCanonicalName() + ".BOB"; + static final String BOB_VALUE = "BobsYourUncle"; + + @Test + void andHowSnapshotBeforeAndAfterTestClass() throws NamingException { + + AndHowTestBaseImpl testBase = new AndHowTestBaseImpl(); + + AndHowCore beforeTheTestCore = AndHowNonProductionUtil.getAndHowCore(); + Properties originalProperties = System.getProperties(); + Level beforeClassLogLevel = Logger.getGlobal().getLevel(); //store log level before class + + try { + + //We shouldn't have this property before we start + assertNull(System.getProperty(BOB_NAME)); + + //Take the snapshot + AndHowTestBaseImpl.andHowSnapshotBeforeTestClass(); + + //Sys props - completely mess them up - reset should reset them... + System.setProperties(new Properties()); //zap all properties + System.setProperty(BOB_NAME, BOB_VALUE); + + //Now build w/ the new SystemProperties + NonProductionConfig.instance().group(SimpleConfig.class).forceBuild(); + + + //Are the sysProps messed up just like we did above? + assertEquals(BOB_VALUE, System.getProperty(BOB_NAME)); + assertEquals(1, System.getProperties().size()); + + //Did the AndHow Property get set? + assertEquals(BOB_VALUE, SimpleConfig.BOB.getValue()); + + //Change the global log level + if (beforeClassLogLevel == null || !beforeClassLogLevel.equals(Level.FINEST)) { + Logger.getGlobal().setLevel(Level.FINEST); + } else { + Logger.getGlobal().setLevel(Level.FINER); + } + + //Is the AndHowCore different than what it was originally? + assertFalse(beforeTheTestCore == AndHowNonProductionUtil.getAndHowCore()); + + // + //reset + AndHowTestBaseImpl.resetAndHowSnapshotAfterTestClass(); + + + //Verify we have the exact same set of SysProps after the reset + assertEquals(originalProperties.size(), System.getProperties().size()); + assertTrue(originalProperties.entrySet().containsAll(System.getProperties().entrySet())); + + //Verify the core is back to the original + assertTrue(beforeTheTestCore == AndHowNonProductionUtil.getAndHowCore()); + + //Did the log level get reset? + assertEquals(beforeClassLogLevel, Logger.getGlobal().getLevel()); + + } finally { + AndHowNonProductionUtil.setAndHowCore(beforeTheTestCore); + System.setProperties(originalProperties); + } + + } + + @Test + void andHowSnapshotBeforeAndAfterSingleTest() { + AndHowTestBaseImpl testBase = new AndHowTestBaseImpl(); + doAndHowSnapshotBeforeAndAfterSingleTest(testBase); + } + + void doAndHowSnapshotBeforeAndAfterSingleTest(AndHowTestBaseImpl testBase) { + + AndHowCore beforeTheTestCore = AndHowNonProductionUtil.getAndHowCore(); + Properties originalProperties = System.getProperties(); + + try { + + //We shouldn't have this property before we start + assertNull(System.getProperty(BOB_NAME)); + + //Take the snapshot + testBase.andHowSnapshotBeforeSingleTest(); + + //Sys props - completely mess them up - reset should reset them... + System.setProperties(new Properties()); //zap all properties + System.setProperty(BOB_NAME, BOB_VALUE); + + //Now build w/ the new SystemProperties + NonProductionConfig.instance().group(SimpleConfig.class).forceBuild(); + + + //Are the sysProps messed up just like we did above? + assertEquals(BOB_VALUE, System.getProperty(BOB_NAME)); + assertEquals(1, System.getProperties().size()); + + //Did the AndHow Property get set? + assertEquals(BOB_VALUE, SimpleConfig.BOB.getValue()); + + //Is the AndHowCore different than what it was originally? + assertFalse(beforeTheTestCore == AndHowNonProductionUtil.getAndHowCore()); + + // + //reset + testBase.resetAndHowSnapshotAfterSingleTest(); + + + //Verify we have the exact same set of SysProps after the reset + assertEquals(originalProperties.size(), System.getProperties().size()); + assertTrue(originalProperties.entrySet().containsAll(System.getProperties().entrySet())); + + //Verify the core is back to the original + assertTrue(beforeTheTestCore == AndHowNonProductionUtil.getAndHowCore()); + + } finally { + AndHowNonProductionUtil.setAndHowCore(beforeTheTestCore); + System.setProperties(originalProperties); + } + + } + + /** + * Simulating the progression of a test where jndi properties are + * set at the start of a single test. + * @throws NamingException + */ + @Test + public void getJndiTest() throws NamingException { + + AndHowTestBaseImpl testBase = new AndHowTestBaseImpl(); + + + try { + + //Start of test class and before a test + AndHowTestBaseImpl.andHowSnapshotBeforeTestClass(); + testBase.andHowSnapshotBeforeSingleTest(); + + //Now we are setting up for a single test w/ a JNDI property + SimpleNamingContextBuilder jndi = testBase.getJndi(); + jndi.bind("java:" + BOB_NAME, BOB_VALUE + "_JNDI"); + jndi.activate(); + + //Can we read the JNDI property? + final InitialContext ctx = new InitialContext(); // Jndi Context should have set value + assertEquals(BOB_VALUE + "_JNDI", ctx.lookup("java:" + BOB_NAME)); + + // + //Now build AndHow - should see JNDI property + NonProductionConfig.instance().group(SimpleConfig.class).forceBuild(); + + //Did the AndHow Property get set? + assertEquals(BOB_VALUE + "_JNDI", SimpleConfig.BOB.getValue()); + + //End of one test and the start of another + testBase.resetAndHowSnapshotAfterSingleTest(); + testBase.andHowSnapshotBeforeSingleTest(); + + assertThrows(NamingException.class, () -> + ctx.lookup("java:" + BOB_NAME) + ); + + // + //Now build AndHow again - 'BOB' should be empty- should see JNDI property + NonProductionConfig.instance().group(SimpleConfig.class).forceBuild(); + + assertNull(SimpleConfig.BOB.getValue()); + + //End of one test and the start of another + testBase.resetAndHowSnapshotAfterSingleTest(); + testBase.andHowSnapshotBeforeSingleTest(); + + //Activate JNDI again - it should still be empty + testBase.getJndi().activate(); + + //should still be empty on the original context + assertThrows(NamingException.class, () -> + ctx.lookup("java:" + BOB_NAME) + ); + + //... and on a new context + final InitialContext ctx2 = new InitialContext(); + assertThrows(NamingException.class, () -> + ctx2.lookup("java:" + BOB_NAME) + ); + + } finally { + testBase.resetAndHowSnapshotAfterSingleTest(); + AndHowTestBaseImpl.resetAndHowSnapshotAfterTestClass(); + } + } + +} \ No newline at end of file diff --git a/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/AndHowTestBaseTest.java b/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/AndHowTestBaseTest.java new file mode 100644 index 00000000..8c96fc49 --- /dev/null +++ b/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/AndHowTestBaseTest.java @@ -0,0 +1,66 @@ +package org.yarnandtail.andhow; + +import org.junit.jupiter.api.Test; + +import java.util.Properties; + +import static org.junit.jupiter.api.Assertions.*; + +/** + * The implementation is all in the base class, so here tests are minimal + * to make sure the base class is being called. + */ +class AndHowTestBaseTest { + + static final String BOB = "bob"; + + @Test + void andHowSnapshotBeforeAndAfterTestClass() { + + Properties originalProperties = System.getProperties(); + + try { + + //We shouldn't have this property before we start + assertNull(System.getProperty(BOB)); + + //Take the snapshot + AndHowTestBase.andHowSnapshotBeforeTestClass(); + + //Sys props - completely mess them up - reset should reset them... + System.setProperties(new Properties()); //zap all properties + System.setProperty(BOB, BOB); + + + //Are the sysProps messed up just like we did above? + assertEquals(BOB, System.getProperty(BOB)); + assertEquals(1, System.getProperties().size()); + + // + //reset + AndHowTestBase.resetAndHowSnapshotAfterTestClass(); + + + //Verify we have the exact same set of SysProps after the reset + assertEquals(originalProperties.size(), System.getProperties().size()); + assertTrue(originalProperties.entrySet().containsAll(System.getProperties().entrySet())); + + } finally { + System.setProperties(originalProperties); + } + + } + + + @Test + void andHowSnapshotBeforeAndAfterSingleTest() { + + AndHowTestBase testBase = new AndHowTestBase(); + AndHowTestBaseImplTest implTest = new AndHowTestBaseImplTest(); + + implTest.doAndHowSnapshotBeforeAndAfterSingleTest(testBase); + + } + + +} \ No newline at end of file diff --git a/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/AndHowTestingTestBase.java b/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/AndHowTestingTestBase.java index 13939fca..e26f2cad 100644 --- a/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/AndHowTestingTestBase.java +++ b/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/AndHowTestingTestBase.java @@ -1,9 +1,11 @@ package org.yarnandtail.andhow; import java.lang.reflect.Field; -import java.util.Properties; -import javax.naming.NamingException; -import org.junit.*; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.springframework.mock.jndi.SimpleNamingContextBuilder; /** @@ -34,24 +36,24 @@ public class AndHowTestingTestBase { */ private static SimpleNamingContextBuilder builder; - @BeforeClass + @BeforeAll public static void killAndHowStateBeforeClass() { destroyAndHow(); } - @Before + @BeforeEach public void killAndHowStateBeforeTest() { destroyAndHow(); } - @After + @AfterEach public void killAndHowStateAfterTest() { destroyAndHow(); } - @AfterClass + @AfterAll public static void killAndHowStateAfterClass() { destroyAndHow(); } diff --git a/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/NonProductionConfigTest.java b/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/NonProductionConfigTest.java index 6e027b77..05568d2e 100644 --- a/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/NonProductionConfigTest.java +++ b/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/NonProductionConfigTest.java @@ -5,17 +5,16 @@ import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; -import org.junit.Test; +import org.junit.jupiter.api.Test; import org.yarnandtail.andhow.NonProductionConfig.NonProductionConfigImpl; import org.yarnandtail.andhow.api.GroupProxy; import org.yarnandtail.andhow.api.Loader; -import org.yarnandtail.andhow.internal.AndHowCore; import org.yarnandtail.andhow.load.*; import org.yarnandtail.andhow.load.std.StdFixedValueLoader; import org.yarnandtail.andhow.load.std.StdMainStringArgsLoader; import org.yarnandtail.andhow.property.StrProp; -import static org.junit.Assert.*; +import static org.junit.jupiter.api.Assertions.*; import static org.yarnandtail.andhow.AndHowNonProductionUtil.PERMISSION_MSG; /** @@ -57,11 +56,13 @@ public void testAddCmdLineArg() { assertEquals("NULL", kvps.get(2)); } - @Test(expected = RuntimeException.class) + @Test public void testAddCmdLineArgWithNull() { NonProductionConfigImpl config = NonProductionConfig.instance(); - config.addCmdLineArg(null, "one"); - + + assertThrows(RuntimeException.class, () -> { + config.addCmdLineArg(null, "one"); + }); } @Test diff --git a/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/SimpleConfig.java b/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/SimpleConfig.java new file mode 100644 index 00000000..54c29572 --- /dev/null +++ b/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/SimpleConfig.java @@ -0,0 +1,8 @@ +package org.yarnandtail.andhow; + +import org.yarnandtail.andhow.property.StrProp; + +public interface SimpleConfig { + StrProp BOB = StrProp.builder().build(); + +} diff --git a/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeAllTestsExtensionTest.java b/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeAllTestsExtensionTest.java new file mode 100644 index 00000000..6e9f98be --- /dev/null +++ b/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeAllTestsExtensionTest.java @@ -0,0 +1,120 @@ +package org.yarnandtail.andhow.junit5; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; +import org.yarnandtail.andhow.AndHow; +import org.yarnandtail.andhow.AndHowNonProductionUtil; +import org.yarnandtail.andhow.internal.AndHowCore; + + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * Unit tests of this JUnit Extension. + * Beyond unit testing, the extension is used in many of the simulated app tests + * (see that module for usage examples). + */ +class KillAndHowBeforeAllTestsExtensionTest { + + AndHowCore andHowCoreCreatedDuringTest; + + //The expected namespace used to store values within the Extension + ExtensionContext.Namespace expectedNamespace; + + //Store for state variables within the context + ExtensionContext.Store store; + + //The context object that is passed to the test extension + ExtensionContext extensionContext; + + + @BeforeEach + void setUp() { + + // Setup AndHow for the test + assertNull(AndHowNonProductionUtil.getAndHowCore(), + "Just checking - no test should leave AndHow initialized"); + + AndHow.instance(); //force creation + + andHowCoreCreatedDuringTest = AndHowNonProductionUtil.getAndHowCore(); + + assertNotNull(andHowCoreCreatedDuringTest, "Should be non-null because we forced creation"); + + // + // Setup mockito for the test + + store = mock(ExtensionContext.Store.class); + when(store.remove(any(), any())).thenReturn(andHowCoreCreatedDuringTest); + + extensionContext = mock(ExtensionContext.class); + when(extensionContext.getRequiredTestClass()).thenReturn((Class)(this.getClass())); + when(extensionContext.getStore(any())).thenReturn(store); + } + + @AfterEach + void tearDown() { + AndHowNonProductionUtil.destroyAndHowCore(); + } + + @Test + void completeWorkflow() throws Exception { + + KillAndHowBeforeAllTestsExtension theExt = new KillAndHowBeforeAllTestsExtension(); + + // The initial event called on extension by JUnit + theExt.beforeAll(extensionContext); + + assertNull(AndHowNonProductionUtil.getAndHowCore(), + "Extension should have killed the core"); + + // The final event called on the extension by Junit + theExt.afterAll(extensionContext); + + // + // Verify the overall outcome + assertEquals(andHowCoreCreatedDuringTest, AndHowNonProductionUtil.getAndHowCore(), + "Extension should have reinstated the same core instance created in setup"); + + + // + // Verify actions on the store + ArgumentCaptor keyForPut = ArgumentCaptor.forClass(Object.class); + ArgumentCaptor keyForRemove = ArgumentCaptor.forClass(Object.class); + + InOrder orderedStoreCalls = inOrder(store); + orderedStoreCalls.verify(store).put(keyForPut.capture(), eq(andHowCoreCreatedDuringTest)); + orderedStoreCalls.verify(store).remove(keyForRemove.capture(), eq(AndHowCore.class)); + verifyNoMoreInteractions(store); //Really don't want any other interaction w/ the store + + assertEquals(keyForPut.getValue(), keyForRemove.getValue(), + "The keys used for put & remove should be the same"); + + // + // Verify actions on the ExtensionContext + ArgumentCaptor namespace = + ArgumentCaptor.forClass(ExtensionContext.Namespace.class); + + //Each method is called once in beforeAll and afterAll + verify(extensionContext, times(2)).getRequiredTestClass(); //Must be called to figure out the Test class + verify(extensionContext, times(2)).getStore(namespace.capture()); + + //Verify the namespace used + // The namespace is an implementation detail, but must include the Extension class and the + // Tested class (this class in this case). There isn't an easy way to test that minimum spec, + // so here just check for a specific namespace. + expectedNamespace = ExtensionContext.Namespace.create( + KillAndHowBeforeAllTestsExtension.class, + (Class)(this.getClass())); + assertEquals(expectedNamespace, namespace.getValue()); + + + } + + +} \ No newline at end of file diff --git a/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeEachTestExtensionTest.java b/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeEachTestExtensionTest.java new file mode 100644 index 00000000..c43ba8da --- /dev/null +++ b/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeEachTestExtensionTest.java @@ -0,0 +1,128 @@ +package org.yarnandtail.andhow.junit5; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; +import org.yarnandtail.andhow.AndHow; +import org.yarnandtail.andhow.AndHowNonProductionUtil; +import org.yarnandtail.andhow.internal.AndHowCore; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * Unit tests of this JUnit Extension. + * Beyond unit testing, the extension is used in many of the simulated app tests + * (see that module for usage examples). + */ +class KillAndHowBeforeEachTestExtensionTest { + + AndHowCore andHowCoreCreatedDuringTest; + + //The expected namespace used to store values within the Extension + ExtensionContext.Namespace expectedNamespace; + + //Store for state variables within the context + ExtensionContext.Store store; + + //The context object that is passed to the test extension + ExtensionContext extensionContext; + + + @BeforeEach + void setUp() { + + // Setup AndHow for the test + assertNull(AndHowNonProductionUtil.getAndHowCore(), + "Just checking - no test should leave AndHow initialized"); + + AndHow.instance(); //force creation + + andHowCoreCreatedDuringTest = AndHowNonProductionUtil.getAndHowCore(); + + assertNotNull(andHowCoreCreatedDuringTest, "Should be non-null because we forced creation"); + + // + // Setup mockito for the test + + store = mock(ExtensionContext.Store.class); + when(store.remove(any(), any())).thenReturn(andHowCoreCreatedDuringTest); + + extensionContext = mock(ExtensionContext.class); + when(extensionContext.getRequiredTestClass()).thenReturn((Class)(this.getClass())); + when(extensionContext.getStore(any())).thenReturn(store); + } + + @AfterEach + void tearDown() { + AndHowNonProductionUtil.destroyAndHowCore(); + } + + @Test + void completeWorkflow() throws Exception { + + KillAndHowBeforeEachTestExtension theExt = new KillAndHowBeforeEachTestExtension(); + + // The initial event called on extension by JUnit + theExt.beforeAll(extensionContext); + + assertNull(AndHowNonProductionUtil.getAndHowCore(), + "Extension should have killed the core"); + + AndHow.instance(); //force creation again! + + theExt.beforeEach(extensionContext); + + assertNull(AndHowNonProductionUtil.getAndHowCore(), + "Extension should have killed the core... again!"); + + //Note: after each is not implemented b/c its not needed + + // The final event called on the extension by Junit + theExt.afterAll(extensionContext); + + // + // Verify the overall outcome + assertEquals(andHowCoreCreatedDuringTest, AndHowNonProductionUtil.getAndHowCore(), + "Extension should have reinstated the same core instance created in setup"); + + + // + // Verify actions on the store + ArgumentCaptor keyForPut = ArgumentCaptor.forClass(Object.class); + ArgumentCaptor keyForRemove = ArgumentCaptor.forClass(Object.class); + + InOrder orderedStoreCalls = inOrder(store); + orderedStoreCalls.verify(store).put(keyForPut.capture(), eq(andHowCoreCreatedDuringTest)); + orderedStoreCalls.verify(store).remove(keyForRemove.capture(), eq(AndHowCore.class)); + verifyNoMoreInteractions(store); //Really don't want any other interaction w/ the store + + assertEquals(keyForPut.getValue(), keyForRemove.getValue(), + "The keys used for put & remove should be the same"); + + // + // Verify actions on the ExtensionContext + ArgumentCaptor namespace = + ArgumentCaptor.forClass(ExtensionContext.Namespace.class); + + //Each method is called once in beforeAll and afterAll + verify(extensionContext, times(2)).getRequiredTestClass(); //Must be called to figure out the Test class + verify(extensionContext, times(2)).getStore(namespace.capture()); + + //Verify the namespace used + // The namespace is an implementation detail, but must include the Extension class and the + // Tested class (this class in this case). There isn't an easy way to test that minimum spec, + // so here just check for a specific namespace. + expectedNamespace = ExtensionContext.Namespace.create( + KillAndHowBeforeEachTestExtension.class, + (Class)(this.getClass())); + assertEquals(expectedNamespace, namespace.getValue()); + + + } + + +} \ No newline at end of file diff --git a/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeThisTestExtensionTest.java b/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeThisTestExtensionTest.java new file mode 100644 index 00000000..890b5f11 --- /dev/null +++ b/andhow-testing/andhow-test-harness/src/test/java/org/yarnandtail/andhow/junit5/KillAndHowBeforeThisTestExtensionTest.java @@ -0,0 +1,125 @@ +package org.yarnandtail.andhow.junit5; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; +import org.yarnandtail.andhow.AndHow; +import org.yarnandtail.andhow.AndHowNonProductionUtil; +import org.yarnandtail.andhow.internal.AndHowCore; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; + +/** + * Unit tests of this JUnit Extension. + * Beyond unit testing, the extension is used in many of the simulated app tests + * (see that module for usage examples). + */ +class KillAndHowBeforeThisTestExtensionTest { + + AndHowCore andHowCoreCreatedDuringTest; + + //The expected namespace used to store values within the Extension + ExtensionContext.Namespace expectedNamespace; + + //Store for state variables within the context + ExtensionContext.Store store; + + //The context object that is passed to the test extension + ExtensionContext extensionContext; + + + @BeforeEach + void setUp() throws NoSuchMethodException { + + // Setup AndHow for the test + assertNull(AndHowNonProductionUtil.getAndHowCore(), + "Just checking - no test should leave AndHow initialized"); + + AndHow.instance(); //force creation + + andHowCoreCreatedDuringTest = AndHowNonProductionUtil.getAndHowCore(); + + assertNotNull(andHowCoreCreatedDuringTest, "Should be non-null because we forced creation"); + + // + // Setup mockito for the test + + store = mock(ExtensionContext.Store.class); + when(store.remove(any(), any())).thenReturn(andHowCoreCreatedDuringTest); + + extensionContext = mock(ExtensionContext.class); + when(extensionContext.getRequiredTestInstance()).thenReturn(this); + when(extensionContext.getRequiredTestMethod()).thenReturn(this.getClass().getMethod("completeWorkflow")); + when(extensionContext.getStore(any())).thenReturn(store); + } + + @AfterEach + void tearDown() { + AndHowNonProductionUtil.destroyAndHowCore(); + } + + @Test + public void completeWorkflow() throws Exception { + + KillAndHowBeforeThisTestExtension theExt = new KillAndHowBeforeThisTestExtension(); + + // The initial event called on extension by JUnit + theExt.beforeEach(extensionContext); + + assertNull(AndHowNonProductionUtil.getAndHowCore(), + "Extension should have killed the core"); + + // The final event called on the extension by Junit + theExt.afterEach(extensionContext); + + // + // Verify the overall outcome + assertEquals(andHowCoreCreatedDuringTest, AndHowNonProductionUtil.getAndHowCore(), + "Extension should have reinstated the same core instance created in setup"); + + + // + // Verify actions on the store + ArgumentCaptor keyForPut = ArgumentCaptor.forClass(Object.class); + ArgumentCaptor keyForRemove = ArgumentCaptor.forClass(Object.class); + + InOrder orderedStoreCalls = inOrder(store); + orderedStoreCalls.verify(store).put(keyForPut.capture(), eq(andHowCoreCreatedDuringTest)); + orderedStoreCalls.verify(store).remove(keyForRemove.capture(), eq(AndHowCore.class)); + verifyNoMoreInteractions(store); //Really don't want any other interaction w/ the store + + assertEquals(keyForPut.getValue(), keyForRemove.getValue(), + "The keys used for put & remove should be the same"); + + // + // Verify actions on the ExtensionContext + ArgumentCaptor namespace = + ArgumentCaptor.forClass(ExtensionContext.Namespace.class); + + //Each method is called once in beforeAll and afterAll + verify(extensionContext, times(2)).getRequiredTestInstance(); //Must be called to figure out the Test class + verify(extensionContext, times(2)).getStore(namespace.capture()); + + //Verify the namespace used + // The namespace is an implementation detail, but must include the Extension class, the + // instance of the test (this instance)*, and since this extension is specific to the actual + // test method called, the Method of the test (this method). + // There isn't an easy way to test that minimum spec, so here just check for a specific namespace. + // * This extension is different from the other in needing the test instance rather than the + // test class. Since this extension stores values at the instance method level, it is unique + // to that level rather than to the class level. + expectedNamespace = ExtensionContext.Namespace.create( + KillAndHowBeforeThisTestExtension.class, + this, + this.getClass().getMethod("completeWorkflow")); + assertEquals(expectedNamespace, namespace.getValue()); + + + } + + +} \ No newline at end of file diff --git a/andhow/pom.xml b/andhow/pom.xml index b789fb48..de9eaa8b 100644 --- a/andhow/pom.xml +++ b/andhow/pom.xml @@ -4,7 +4,7 @@ org.yarnandtail andhow-parent - 0.4.1-SNAPSHOT + 0.4.2-SNAPSHOT andhow jar @@ -56,19 +56,6 @@ org.apache.maven.plugins maven-javadoc-plugin - - - javadoc-jar - package - - jar - - - - true - - - diff --git a/appveyor.yml b/appveyor.yml index 7cad25ae..81a4571a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -5,17 +5,21 @@ environment: matrix: - JAVA_HOME: C:\Program Files\Java\jdk1.8.0 # Append additional lines defining new values of JAVA_HOME to test multiple versions +branches: + except: + - logo_submission install: # Prepend Java entry, remove Ruby entry (C:\Ruby193\bin;) from PATH - cmd: choco upgrade maven + - cmd: C:\ProgramData\chocolatey\bin\RefreshEnv - cmd: SET PATH=%JAVA_HOME%\bin;%PATH:C:\Ruby193\bin;=%; - cmd: SET MAVEN_OPTS=-XX:MaxPermSize=1g -Xmx2g - cmd: SET JAVA_OPTS=-XX:MaxPermSize=1g -Xmx2g # Print debug info - - cmd: C:\ProgramData\chocolatey\bin\mvn.exe --version + - cmd: mvn --version - cmd: java -version build_script: - - C:\ProgramData\chocolatey\bin\mvn.exe clean install + - mvn clean install cache: - C:\maven\ -> appveyor.yml - C:\Users\appveyor\.m2\ -> pom.xml diff --git a/codecov.yml b/codecov.yml index a64b1ef2..e80bbe1b 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,6 +1,8 @@ ## YAML Template. --- coverage: - range: 0..60 + range: 50..100 round: down - precision: 2 \ No newline at end of file + precision: 2 +ignore: + - "andhow-testing/andhow-annotation-processor-test-harness" diff --git a/logo/AndHow-empty-circle-combination.png b/logo/AndHow-empty-circle-combination.png new file mode 100644 index 00000000..79bf4ec0 Binary files /dev/null and b/logo/AndHow-empty-circle-combination.png differ diff --git a/logo/AndHow-empty-circle.png b/logo/AndHow-empty-circle.png new file mode 100644 index 00000000..234c89b6 Binary files /dev/null and b/logo/AndHow-empty-circle.png differ diff --git a/logo/andhow-empty-circle.svg b/logo/andhow-empty-circle.svg new file mode 100644 index 00000000..7dbfa976 --- /dev/null +++ b/logo/andhow-empty-circle.svg @@ -0,0 +1,260 @@ + + + + + AndHow! + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + AndHow! + + + + + + + ? + + + + + + + AndHow! + ! + + diff --git a/pom.xml b/pom.xml index b53c81e0..f4c41da7 100644 --- a/pom.xml +++ b/pom.xml @@ -2,7 +2,7 @@ 4.0.0 org.yarnandtail andhow-parent - 0.4.1-SNAPSHOT + 0.4.2-SNAPSHOT pom AndHow Parent Project @@ -72,6 +72,9 @@ UTF-8 + 1.8 + 1.8 + none @@ -95,10 +98,47 @@ + + + + org.junit.platform + junit-platform-launcher + 1.7.2 + test + + + org.junit.jupiter + junit-jupiter-api + 5.7.2 + test + + + org.junit.jupiter + junit-jupiter-engine + 5.7.2 + test + + + org.hamcrest + hamcrest + 2.2 + test + + + junit junit - 4.12 + 4.13.1 + test + + + org.junit.vintage + junit-vintage-engine + 5.7.2 test @@ -110,17 +150,64 @@ commons-io commons-io - 2.6 + 2.7 test + com.google.testing.compile compile-testing 0.15 test + + + junit + junit + + + + + org.mockito + mockito-all + 1.10.19 + test + + + com.github.stefanbirkner + system-lambda + 1.2.0 + test + + + + + org.junit.platform + junit-platform-launcher + + + org.junit.jupiter + junit-jupiter-engine + + + org.junit.jupiter + junit-jupiter-api + + + org.hamcrest + hamcrest + + + org.mockito + mockito-all + + + com.github.stefanbirkner + system-lambda + + @@ -133,17 +220,31 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.0.1 - - -Xdoclint:none - true - + 3.3.0 + + + javadoc-jar + package + + jar + + true + + true + none + true + + + - true maven-deploy-plugin 2.8.2 + + maven-assembly-plugin + 3.3.0 + @@ -161,16 +262,22 @@ - 3.2.2 + (3.5,4.0) - [1.8.0,1.9) + + [1.8.0,16) + + org.apache.maven.plugins + maven-surefire-plugin + 3.0.0-M5 + org.codehaus.mojo cobertura-maven-plugin @@ -213,6 +320,17 @@ deploy + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.7 + true + + ossrh + https://oss.sonatype.org/ + false + + @@ -237,17 +355,6 @@ org.apache.maven.plugins maven-javadoc-plugin - - - attach-javadocs - - jar - - - - - -Xdoclint:none -