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