001/* 002 * Copyright (C) 2022 - 2023, 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.Inherited; 022import java.lang.annotation.Retention; 023import java.lang.annotation.RetentionPolicy; 024import java.lang.annotation.Target; 025import org.apiguardian.api.API; 026import org.apiguardian.api.API.Status; 027import org.junit.jupiter.api.Tag; 028import org.junit.jupiter.api.Tags; 029import org.junit.jupiter.api.TestTemplate; 030import org.junit.jupiter.api.condition.DisabledInNativeImage; 031import org.junit.jupiter.params.ParameterizedTest; 032import org.junit.jupiter.params.provider.ArgumentsSource; 033 034/** 035 * Annotation that can be applied to a JUnit parameterized test to invoke that test case across 036 * multiple compilers, each configured to a specific version in a range of Java language versions. 037 * 038 * <p>This will also add the {@code "java-compiler-testing-test"} tag and {@code "javac-test"} 039 * tags to your test method, meaning you can instruct your IDE or build system to optionally only 040 * run tests annotated with this method for development purposes. As an example, Maven Surefire 041 * could be instructed to only run these tests by passing {@code -Dgroup="javac-test"} to Maven. 042 * 043 * <p>If your build is running in a GraalVM Native Image, then this test will not execute, as 044 * the <em>Java Compiler Testing</em> API is not yet tested within Native Images. 045 * 046 * <p>For example, to run a simple test on Java 11 through 17 (inclusive): 047 * 048 * <pre><code> 049 * class SomeTest { 050 * {@literal @JavacCompilerTest(minVersion = 11, maxVersion = 17)} 051 * void canCompileHelloWorld(JctCompiler<?, ?>> compiler) { 052 * // Given 053 * try (var workspace = Workspaces.newWorkspace()) { 054 * workspace 055 * .createFile("org", "example", "HelloWorld.java") 056 * .withContents(""" 057 * package org.example; 058 * 059 * public class HelloWorld { 060 * public static void main(String[] args) { 061 * System.out.println("Hello, World!"); 062 * } 063 * } 064 * """); 065 * 066 * var compilation = compiler.compile(workspace); 067 * 068 * assertThat(compilation) 069 * .isSuccessfulWithoutWarnings(); 070 * } 071 * } 072 * } 073 * </code></pre> 074 * 075 * @author Ashley Scopes 076 * @since 0.0.1 077 */ 078@API(since = "0.0.1", status = Status.STABLE) 079@ArgumentsSource(JavacCompilersProvider.class) 080@DisabledInNativeImage 081@Documented 082@Inherited 083@ParameterizedTest(name = "for compiler \"{0}\"") 084@Retention(RetentionPolicy.RUNTIME) 085@Target({ElementType.ANNOTATION_TYPE, ElementType.METHOD, ElementType.TYPE}) 086@TestTemplate 087@Tags({ 088 @Tag("java-compiler-testing-test"), 089 @Tag("javac-test") 090}) 091public @interface JavacCompilerTest { 092 093 /** 094 * Minimum version to use (inclusive). 095 * 096 * <p>By default, it will use the lowest possible version supported by the compiler. This 097 * varies between versions of the JDK that are in use. 098 * 099 * <p>If the version is lower than the minimum supported version, then the minimum supported 100 * version of the compiler will be used instead. This enables writing tests that will work on 101 * a range of JDKs during builds without needing to duplicate the test to satisfy different 102 * JDK supported version ranges. 103 * 104 * @return the minimum version. 105 */ 106 int minVersion() default Integer.MIN_VALUE; 107 108 /** 109 * Maximum version to use (inclusive). 110 * 111 * <p>By default, it will use the highest possible version supported by the compiler. This 112 * varies between versions of the JDK that are in use. 113 * 114 * <p>If the version is higher than the maximum supported version, then the maximum supported 115 * version of the compiler will be used instead. This enables writing tests that will work on 116 * a range of JDKs during builds without needing to duplicate the test to satisfy different 117 * JDK supported version ranges. 118 * 119 * @return the maximum version. 120 */ 121 int maxVersion() default Integer.MAX_VALUE; 122 123 /** 124 * Get an array of compiler configurer classes to apply in-order before starting the test. 125 * 126 * <p>Each configurer must have a public no-args constructor, and their package must be 127 * open to this module if JPMS modules are in-use, for example: 128 * 129 * <pre><code> 130 * module mytests { 131 * requires io.github.ascopes.jct; 132 * requires org.junit.jupiter.api; 133 * 134 * opens org.example.mytests to io.github.ascopes.jct; 135 * } 136 * </code></pre> 137 * 138 * An example of usage: 139 * 140 * <pre><code> 141 * public class WerrorConfigurer implements JctCompilerConfigurer<RuntimeException> { 142 * {@literal @Override} 143 * public void configure(JctCompiler<?, ?> compiler) { 144 * compiler.failOnWarnings(true); 145 * } 146 * } 147 * 148 * // ... 149 * 150 * class SomeTest { 151 * {@literal @JavacCompilerTest(configurers = WerrorConfigurer.class)} 152 * void someTest(JctCompiler<?, ?> compiler) { 153 * // ... 154 * } 155 * } 156 * </code></pre> 157 * 158 * @return an array of classes to run to configure the compiler. These run in the given order. 159 */ 160 Class<? extends JctCompilerConfigurer<?>>[] configurers() default {}; 161 162 /** 163 * Whether we need to support modules or not. 164 * 165 * <p>Setting this to true will skip any versions of the compiler that do not support JPMS 166 * modules. 167 * 168 * @return {@code true} if we need to support modules, or {@code false} if we do not. 169 * @deprecated this will be removed in a future release, since Java 8 is reaching end-of-life. 170 */ 171 @Deprecated(forRemoval = true, since = "0.1.0") 172 boolean modules() default false; 173 174 /** 175 * The version strategy to use. 176 * 177 * <p>This determines whether the version number being iterated across specifies the 178 * release, source, target, or source and target versions. 179 * 180 * <p>The default is to specify the release. 181 * 182 * @return the version strategy to use. 183 */ 184 VersionStrategy versionStrategy() default VersionStrategy.RELEASE; 185}