From bed820214edbb412ba81b3eb5007dbd4bbef2f41 Mon Sep 17 00:00:00 2001 From: Andy Boothe Date: Wed, 8 Jan 2025 14:17:08 -0600 Subject: [PATCH] adopt new test framework for rest of environment variable tests --- .../EnvironmentVariableProcessorTest.java | 419 +++++++++--------- 1 file changed, 209 insertions(+), 210 deletions(-) diff --git a/rapier-environment-variable-compiler/src/test/java/rapier/envvar/compiler/EnvironmentVariableProcessorTest.java b/rapier-environment-variable-compiler/src/test/java/rapier/envvar/compiler/EnvironmentVariableProcessorTest.java index edd7708..f3ebe2c 100644 --- a/rapier-environment-variable-compiler/src/test/java/rapier/envvar/compiler/EnvironmentVariableProcessorTest.java +++ b/rapier-environment-variable-compiler/src/test/java/rapier/envvar/compiler/EnvironmentVariableProcessorTest.java @@ -46,28 +46,19 @@ import rapier.core.DaggerTestBase; public class EnvironmentVariableProcessorTest extends DaggerTestBase { - @Override - protected List getCompileClasspath() throws FileNotFoundException { - List result = new ArrayList<>(); - result.addAll(super.getCompileClasspath()); - result.add(resolveProjectFile("../rapier-environment-variable/target/classes")); - return unmodifiableList(result); - } - @Test public void givenSimpleComponentWithEnvironmentVariableWithoutDefaultValue_whenCompile_thenExpectedtModuleIsGenerated() throws IOException { // Define the source file to test - final JavaFileObject source = - JavaFileObjects.forSourceString("com.example.ExampleComponent", """ - package com.example; + final JavaFileObject source = prepareSourceFile(""" + package com.example; - @dagger.Component(modules = {RapierExampleComponentEnvironmentVariableModule.class}) - public interface ExampleComponent { - @rapier.envvar.EnvironmentVariable("FOO_BAR") - public Integer provisionFooBarAsInt(); - } - """); + @dagger.Component(modules = {RapierExampleComponentEnvironmentVariableModule.class}) + public interface ExampleComponent { + @rapier.envvar.EnvironmentVariable("FOO_BAR") + public Integer provisionFooBarAsInt(); + } + """); // Run the annotation processor final Compilation compilation = doCompile(source); @@ -128,169 +119,29 @@ public String provideEnvironmentVariableFooBarAsString() { """)); } - private Compilation doCompile(JavaFileObject... source) throws IOException { - return Compiler.javac().withClasspath(getCompileClasspath()) - .withProcessors(getAnnotationProcessors()).compile(source); - } - - private String doRun(Compilation compilation) throws IOException { - final List classpathAsFiles = getRunClasspath(compilation); - - final List classpathAsUrls = new ArrayList<>(); - for (File file : classpathAsFiles) - classpathAsUrls.add(file.toURI().toURL()); - - final List mains = new ArrayList<>(); - compilation.sourceFiles().stream().filter(EnvironmentVariableProcessorTest::containsMainMethod) - .forEach(mains::add); - compilation.generatedSourceFiles().stream() - .filter(EnvironmentVariableProcessorTest::containsMainMethod).forEach(mains::add); - if (mains.isEmpty()) - throw new IllegalArgumentException("No main method found"); - if (mains.size() > 1) - throw new IllegalArgumentException("Multiple main methods found"); - final JavaFileObject main = mains.get(0); - - try (URLClassLoader classLoader = new URLClassLoader(classpathAsUrls.toArray(URL[]::new), - ClassLoader.getPlatformClassLoader())) { - - final Class mainClass = classLoader.loadClass(toQualifiedClassName(main)); - - // Run the main method of the specified class - final Method mainMethod = mainClass.getDeclaredMethod("main", String[].class); - - final ByteArrayOutputStream stdout = new ByteArrayOutputStream(); - final ByteArrayOutputStream stderr = new ByteArrayOutputStream(); - synchronized (System.class) { - final PrintStream stdout0 = System.out; - final PrintStream stderr0 = System.err; - try { - System.setOut(new PrintStream(stdout)); - System.setErr(new PrintStream(stderr)); - mainMethod.invoke(null, (Object) new String[] {}); - } finally { - System.setOut(stdout0); - System.setErr(stderr0); - } - } - - final byte[] stdoutBytes = stdout.toByteArray(); - final byte[] stderrBytes = stderr.toByteArray(); - - if (stderrBytes.length > 0) - System.err.write(stderrBytes); - - return new String(stdoutBytes, StandardCharsets.UTF_8); - } catch (RuntimeException e) { - throw e; - } catch (IOException e) { - throw new UncheckedIOException(e); - } catch (InvocationTargetException e) { - Throwable cause = e.getCause(); - if (cause instanceof RuntimeException) - throw (RuntimeException) cause; - if (cause instanceof IOException) - throw new UncheckedIOException((IOException) cause); - throw new IllegalArgumentException( - "Execution of invalid compilation units failed with exception", cause); - } catch (Exception e) { - throw new IllegalArgumentException( - "Execution of invalid compilation units failed with exception", e); - } - } - - /** - * Check if a JavaFileObject contains a main method. A main method is considered to be present if - * the file contains the literal string {@code "public static void main(String[] args)"}. - * - * @param file the file to check - * @return {@code true} if the file contains a main method, {@code false} otherwise - * @throws UncheckedIOException if an IOException occurs while reading the file - */ - private static boolean containsMainMethod(JavaFileObject file) { - try { - return file.getCharContent(true).toString() - .contains("public static void main(String[] args)"); - } catch (IOException e) { - // This really ought never to happen, since it's all in memory. - throw new UncheckedIOException("Failed to read JavaFileObject contents", e); - } - } - - /** - * Extracts the qualified Java class name from a JavaFileObject. - * - * @param file the JavaFileObject representing the Java source code - * @return the qualified class name - */ - private static String toQualifiedClassName(JavaFileObject file) { - // Get the name of the JavaFileObject - String fileName = file.getName(); - - // Remove directories or prefixes before the package - // Example: "/com/example/HelloWorld.java" - // becomes "com/example/HelloWorld.java" - if (fileName.startsWith("/")) { - fileName = fileName.substring(1); - } - - // Remove the ".java" extension - if (fileName.endsWith(".java")) { - fileName = fileName.substring(0, fileName.length() - ".java".length()); - } - - // Replace '/' with '.' to form package and class hierarchy - fileName = fileName.replace("/", "."); - - return fileName; - } - - private static final Pattern PACKAGE_DECLARATION_PATTERN = - Pattern.compile("^package\\s+(\\S+)\\s*;", Pattern.MULTILINE); - private static final Pattern CLASS_DECLARATION_PATTERN = - Pattern.compile("^public\\s+(?:class|interface)\\s+(\\S+)\\s*\\{", Pattern.MULTILINE); - - private static JavaFileObject prepareSourceFile(String sourceCode) { - final String packageName = PACKAGE_DECLARATION_PATTERN.matcher(sourceCode).results().findFirst() - .map(m -> m.group(1)).orElse(null); - - final String simpleClassName = CLASS_DECLARATION_PATTERN.matcher(sourceCode).results() - .findFirst().map(m -> m.group(1)).orElse(null); - if (simpleClassName == null) - throw new IllegalArgumentException("Failed to detect class name"); - - final String qualifiedClassName = - packageName != null ? packageName + "." + simpleClassName : simpleClassName; - - return JavaFileObjects.forSourceString(qualifiedClassName, sourceCode); - } - - @Test - public void givenSimpleComponentWithEnvironmentVariableWithDefaultValue_whenCompile_thenExpectedtModuleIsGenerated() { + public void givenSimpleComponentWithEnvironmentVariableWithDefaultValue_whenCompile_thenExpectedtModuleIsGenerated() + throws IOException { // Define the source file to test - final JavaFileObject source = - JavaFileObjects.forSourceString("com.example.ExampleComponent", """ - package com.example; + final JavaFileObject source = prepareSourceFile(""" + package com.example; - @dagger.Component - public interface ExampleComponent { - @rapier.envvar.EnvironmentVariable(value="FOO_BAR", defaultValue="42") - public Integer provisionFooBarAsInt(); - } - """); + @dagger.Component(modules={RapierExampleComponentEnvironmentVariableModule.class}) + public interface ExampleComponent { + @rapier.envvar.EnvironmentVariable(value="FOO_BAR", defaultValue="42") + public Integer provisionFooBarAsInt(); + } + """); // Run the annotation processor - final Compilation compilation = - Compiler.javac().withProcessors(new EnvironmentVariableProcessor()).compile(source); + final Compilation compilation = doCompile(source); // Assert the compilation succeeded assertThat(compilation).succeeded(); assertThat(compilation) .generatedSourceFile("com.example.RapierExampleComponentEnvironmentVariableModule") - .hasSourceEquivalentTo(JavaFileObjects.forSourceString( - "com.example.RapierExampleComponentEnvironmentVariableModule", + .hasSourceEquivalentTo(prepareSourceFile( """ package com.example; @@ -337,35 +188,36 @@ public String provideEnvironmentVariableFooBarWithDefaultValue92cfcebAsString() public void givenSimpleComponentWithEnvironmentVariableWithGivenValue_whenCompileAndRun_thenExpectedtOutput() throws IOException { // Define the source file to test - final String componentSource = """ + final JavaFileObject componentSource = prepareSourceFile(""" @dagger.Component(modules={RapierExampleComponentEnvironmentVariableModule.class}) public interface ExampleComponent { @javax.annotation.Nullable @rapier.envvar.EnvironmentVariable("FOO_BAR") public Integer provisionFooBarAsInt(); } - """; - - final String appSource = - """ - import java.util.Map; - - public class App { - public static void main(String[] args) { - ExampleComponent component = DaggerExampleComponent.builder() - .rapierExampleComponentEnvironmentVariableModule(new RapierExampleComponentEnvironmentVariableModule(Map.of("FOO_BAR", "42"))) - .build(); - System.out.println(component.provisionFooBarAsInt()); - } + """); + + final JavaFileObject appSource = prepareSourceFile(""" + import java.util.Map; + + public class App { + public static void main(String[] args) { + ExampleComponent component = DaggerExampleComponent.builder() + .rapierExampleComponentEnvironmentVariableModule( + new RapierExampleComponentEnvironmentVariableModule(Map.of("FOO_BAR", "42"))) + .build(); + System.out.println(component.provisionFooBarAsInt()); } - """; + } + """); + + final Compilation compilation = doCompile(componentSource, appSource); - final String output = compileAndRunSourceCode(List.of(componentSource, appSource), - List.of("rapier.envvar.compiler.EnvironmentVariableProcessor", - DAGGER_COMPONENT_ANNOTATION_PROCESSOR), - List.of(resolveProjectFile("../rapier-environment-variable/target/classes"))).trim(); + assertThat(compilation).succeeded(); + + final String appOutput = doRun(compilation).trim(); - assertEquals("42", output); + assertEquals("42", appOutput); } @Test @@ -393,39 +245,35 @@ public static void main(String[] args) { } """); - Compilation compilation = doCompile(componentSource, appSource); + final Compilation compilation = doCompile(componentSource, appSource); assertThat(compilation).succeeded(); - final String output = doRun(compilation).trim(); + final String appOutput = doRun(compilation).trim(); - assertEquals("43", output); + assertEquals("43", appOutput); } @Test public void givenComponentWithInconsistentEnvironmentVariableParameterRequirednessFromNullable_whenCompile_thenCompileWarning() throws IOException { // Define the source file to test - final JavaFileObject source = - JavaFileObjects.forSourceString("com.example.ExampleComponent", """ - package com.example; + final JavaFileObject source = prepareSourceFile(""" + package com.example; - @dagger.Component(modules={RapierExampleComponentEnvironmentVariableModule.class}) - public interface ExampleComponent { - @javax.annotation.Nullable - @rapier.envvar.EnvironmentVariable(value="FOO_BAR") - public Integer provisionFooBarAsInt(); + @dagger.Component(modules={RapierExampleComponentEnvironmentVariableModule.class}) + public interface ExampleComponent { + @javax.annotation.Nullable + @rapier.envvar.EnvironmentVariable(value="FOO_BAR") + public Integer provisionFooBarAsInt(); - @rapier.envvar.EnvironmentVariable(value="FOO_BAR") - public String provisionFooBarAsString(); - } - """); + @rapier.envvar.EnvironmentVariable(value="FOO_BAR") + public String provisionFooBarAsString(); + } + """); - // Run the annotation processor - final Compilation compilation = - Compiler.javac().withProcessors(new EnvironmentVariableProcessor()).compile(source); + final Compilation compilation = doCompile(source); - // Assert the compilation succeeded assertThat(compilation).succeeded(); assertTrue( @@ -454,8 +302,7 @@ public interface ExampleComponent { """); // Run the annotation processor - final Compilation compilation = - Compiler.javac().withProcessors(new EnvironmentVariableProcessor()).compile(source); + final Compilation compilation = doCompile(source); // Assert the compilation succeeded assertThat(compilation).succeeded(); @@ -466,4 +313,156 @@ public interface ExampleComponent { assertTrue(compilation.warnings().stream().anyMatch(e -> e.getMessage(Locale.getDefault()) .equals("Effectively required environment variable FOO_BAR has default value"))); } + + /** + * We need to include the generated classes from the rapier-environment-variable module in the + * classpath for our tests. + */ + @Override + protected List getCompileClasspath() throws FileNotFoundException { + List result = new ArrayList<>(); + result.addAll(super.getCompileClasspath()); + result.add(resolveProjectFile("../rapier-environment-variable/target/classes")); + return unmodifiableList(result); + } + + // UTILITY /////////////////////////////////////////////////////////////////////////////////////// + + + private Compilation doCompile(JavaFileObject... source) throws IOException { + return Compiler.javac().withClasspath(getCompileClasspath()) + .withProcessors(getAnnotationProcessors()).compile(source); + } + + private String doRun(Compilation compilation) throws IOException { + final List classpathAsFiles = getRunClasspath(compilation); + + final List classpathAsUrls = new ArrayList<>(); + for (File file : classpathAsFiles) + classpathAsUrls.add(file.toURI().toURL()); + + final List mains = new ArrayList<>(); + compilation.sourceFiles().stream().filter(EnvironmentVariableProcessorTest::containsMainMethod) + .forEach(mains::add); + compilation.generatedSourceFiles().stream() + .filter(EnvironmentVariableProcessorTest::containsMainMethod).forEach(mains::add); + if (mains.isEmpty()) + throw new IllegalArgumentException("No main method found"); + if (mains.size() > 1) + throw new IllegalArgumentException("Multiple main methods found"); + final JavaFileObject main = mains.get(0); + + try (URLClassLoader classLoader = new URLClassLoader(classpathAsUrls.toArray(URL[]::new), + ClassLoader.getPlatformClassLoader())) { + + final Class mainClass = classLoader.loadClass(toQualifiedClassName(main)); + + // Run the main method of the specified class + final Method mainMethod = mainClass.getDeclaredMethod("main", String[].class); + + final ByteArrayOutputStream stdout = new ByteArrayOutputStream(); + final ByteArrayOutputStream stderr = new ByteArrayOutputStream(); + synchronized (System.class) { + final PrintStream stdout0 = System.out; + final PrintStream stderr0 = System.err; + try { + System.setOut(new PrintStream(stdout)); + System.setErr(new PrintStream(stderr)); + mainMethod.invoke(null, (Object) new String[] {}); + } finally { + System.setOut(stdout0); + System.setErr(stderr0); + } + } + + final byte[] stdoutBytes = stdout.toByteArray(); + final byte[] stderrBytes = stderr.toByteArray(); + + if (stderrBytes.length > 0) + System.err.write(stderrBytes); + + return new String(stdoutBytes, StandardCharsets.UTF_8); + } catch (RuntimeException e) { + throw e; + } catch (IOException e) { + throw new UncheckedIOException(e); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if (cause instanceof RuntimeException) + throw (RuntimeException) cause; + if (cause instanceof IOException) + throw new UncheckedIOException((IOException) cause); + throw new IllegalArgumentException( + "Execution of invalid compilation units failed with exception", cause); + } catch (Exception e) { + throw new IllegalArgumentException( + "Execution of invalid compilation units failed with exception", e); + } + } + + /** + * Check if a JavaFileObject contains a main method. A main method is considered to be present if + * the file contains the literal string {@code "public static void main(String[] args)"}. + * + * @param file the file to check + * @return {@code true} if the file contains a main method, {@code false} otherwise + * @throws UncheckedIOException if an IOException occurs while reading the file + */ + private static boolean containsMainMethod(JavaFileObject file) { + try { + return file.getCharContent(true).toString() + .contains("public static void main(String[] args)"); + } catch (IOException e) { + // This really ought never to happen, since it's all in memory. + throw new UncheckedIOException("Failed to read JavaFileObject contents", e); + } + } + + /** + * Extracts the qualified Java class name from a JavaFileObject. + * + * @param file the JavaFileObject representing the Java source code + * @return the qualified class name + */ + private static String toQualifiedClassName(JavaFileObject file) { + // Get the name of the JavaFileObject + String fileName = file.getName(); + + // Remove directories or prefixes before the package + // Example: "/com/example/HelloWorld.java" + // becomes "com/example/HelloWorld.java" + if (fileName.startsWith("/")) { + fileName = fileName.substring(1); + } + + // Remove the ".java" extension + if (fileName.endsWith(".java")) { + fileName = fileName.substring(0, fileName.length() - ".java".length()); + } + + // Replace '/' with '.' to form package and class hierarchy + fileName = fileName.replace("/", "."); + + return fileName; + } + + private static final Pattern PACKAGE_DECLARATION_PATTERN = + Pattern.compile("^package\\s+(\\S+)\\s*;", Pattern.MULTILINE); + private static final Pattern CLASS_DECLARATION_PATTERN = + Pattern.compile("^public\\s+(?:class|interface)\\s+(\\S+)\\s*\\{", Pattern.MULTILINE); + + private static JavaFileObject prepareSourceFile(String sourceCode) { + final String packageName = PACKAGE_DECLARATION_PATTERN.matcher(sourceCode).results().findFirst() + .map(m -> m.group(1)).orElse(null); + + final String simpleClassName = CLASS_DECLARATION_PATTERN.matcher(sourceCode).results() + .findFirst().map(m -> m.group(1)).orElse(null); + if (simpleClassName == null) + throw new IllegalArgumentException("Failed to detect class name"); + + final String qualifiedClassName = + packageName != null ? packageName + "." + simpleClassName : simpleClassName; + + return JavaFileObjects.forSourceString(qualifiedClassName, sourceCode); + } }