001/* 002 * Copyright (C) 2022 - 2024, the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package io.github.ascopes.jct.junit; 017 018import io.github.ascopes.jct.compilers.JctCompilerConfigurer; 019import java.lang.annotation.Documented; 020import java.lang.annotation.ElementType; 021import java.lang.annotation.Retention; 022import java.lang.annotation.RetentionPolicy; 023import java.lang.annotation.Target; 024import org.junit.jupiter.api.Tag; 025import org.junit.jupiter.api.Tags; 026import org.junit.jupiter.api.TestTemplate; 027import org.junit.jupiter.api.condition.DisabledInNativeImage; 028import org.junit.jupiter.params.ParameterizedTest; 029import org.junit.jupiter.params.provider.ArgumentsSource; 030 031/** 032 * Annotation that can be applied to a JUnit parameterized test to invoke that test case across 033 * multiple compilers, each configured to a specific version in a range of Java language versions. 034 * 035 * <p>This will also add the {@code "java-compiler-testing-test"} tag and {@code "javac-test"} 036 * tags to your test method, meaning you can instruct your IDE or build system to optionally only 037 * run tests annotated with this method for development purposes. As an example, Maven Surefire 038 * could be instructed to only run these tests by passing {@code -Dgroup="javac-test"} to Maven. 039 * 040 * <p>If your build is running in a GraalVM Native Image, then this test will not execute, as 041 * the <em>Java Compiler Testing</em> API is not yet tested within Native Images. 042 * 043 * <p>For example, to run a simple test on Java 11 through 17 (inclusive): 044 * 045 * <pre><code> 046 * class SomeTest { 047 * {@literal @JavacCompilerTest(minVersion = 11, maxVersion = 17)} 048 * void canCompileHelloWorld(JctCompiler> compiler) { 049 * // Given 050 * try (var workspace = Workspaces.newWorkspace()) { 051 * workspace 052 * .createFile("org", "example", "HelloWorld.java") 053 * .withContents(""" 054 * package org.example; 055 * 056 * public class HelloWorld { 057 * public static void main(String[] args) { 058 * System.out.println("Hello, World!"); 059 * } 060 * } 061 * """); 062 * 063 * var compilation = compiler.compile(workspace); 064 * 065 * assertThat(compilation) 066 * .isSuccessfulWithoutWarnings(); 067 * } 068 * } 069 * } 070 * </code></pre> 071 * 072 * @author Ashley Scopes 073 * @since 0.0.1 074 */ 075@ArgumentsSource(JavacCompilersProvider.class) 076@DisabledInNativeImage 077@Documented 078@ParameterizedTest(name = "for compiler \"{0}\"") 079@Retention(RetentionPolicy.RUNTIME) 080@Tags({ 081 @Tag("java-compiler-testing-test"), 082 @Tag("javac-test") 083}) 084@Target({ 085 ElementType.ANNOTATION_TYPE, 086 ElementType.METHOD, 087}) 088@TestTemplate 089public @interface JavacCompilerTest { 090 091 /** 092 * Minimum version to use (inclusive). 093 * 094 * <p>By default, it will use the lowest possible version supported by the compiler. This 095 * varies between versions of the JDK that are in use. 096 * 097 * <p>If the version is lower than the minimum supported version, then the minimum supported 098 * version of the compiler will be used instead. This enables writing tests that will work on a 099 * range of JDKs during builds without needing to duplicate the test to satisfy different JDK 100 * supported version ranges. 101 * 102 * @return the minimum version. 103 */ 104 int minVersion() default Integer.MIN_VALUE; 105 106 /** 107 * Maximum version to use (inclusive). 108 * 109 * <p>By default, it will use the highest possible version supported by the compiler. This 110 * varies between versions of the JDK that are in use. 111 * 112 * <p>If the version is higher than the maximum supported version, then the maximum supported 113 * version of the compiler will be used instead. This enables writing tests that will work on a 114 * range of JDKs during builds without needing to duplicate the test to satisfy different JDK 115 * supported version ranges. 116 * 117 * @return the maximum version. 118 */ 119 int maxVersion() default Integer.MAX_VALUE; 120 121 /** 122 * Get an array of compiler configurer classes to apply in-order before starting the test. 123 * 124 * <p>Each configurer must have a public no-args constructor, and their package must be 125 * open to this module if JPMS modules are in-use, for example: 126 * 127 * <pre><code> 128 * module mytests { 129 * requires io.github.ascopes.jct; 130 * requires org.junit.jupiter.api; 131 * 132 * opens org.example.mytests to io.github.ascopes.jct; 133 * } 134 * </code></pre> 135 * 136 * <p>An example of usage: 137 * 138 * <pre><code> 139 * public class WerrorConfigurer implements JctCompilerConfigurer<RuntimeException> { 140 * {@literal @Override} 141 * public void configure(JctCompiler compiler) { 142 * compiler.failOnWarnings(true); 143 * } 144 * } 145 * 146 * // ... 147 * 148 * class SomeTest { 149 * {@literal @JavacCompilerTest(configurers = WerrorConfigurer.class)} 150 * void someTest(JctCompiler compiler) { 151 * // ... 152 * } 153 * } 154 * </code></pre> 155 * 156 * @return an array of classes to run to configure the compiler. These run in the given order. 157 */ 158 Class<? extends JctCompilerConfigurer<?>>[] configurers() default {}; 159 160 /** 161 * The version strategy to use. 162 * 163 * <p>This determines whether the version number being iterated across specifies the 164 * release, source, target, or source and target versions. 165 * 166 * <p>The default is to specify the release. 167 * 168 * @return the version strategy to use. 169 */ 170 VersionStrategy versionStrategy() default VersionStrategy.RELEASE; 171}