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.apiguardian.api.API; 025import org.apiguardian.api.API.Status; 026import org.junit.jupiter.api.Tag; 027import org.junit.jupiter.api.Tags; 028import org.junit.jupiter.api.TestTemplate; 029import org.junit.jupiter.api.condition.DisabledInNativeImage; 030import org.junit.jupiter.params.ParameterizedTest; 031import org.junit.jupiter.params.provider.ArgumentsSource; 032 033/** 034 * Annotation that can be applied to a JUnit parameterized test to invoke that test case across 035 * multiple compilers, each configured to a specific version in a range of Java language versions. 036 * 037 * <p>This will also add the {@code "java-compiler-testing-test"} tag and {@code "javac-test"} 038 * tags to your test method, meaning you can instruct your IDE or build system to optionally only 039 * run tests annotated with this method for development purposes. As an example, Maven Surefire 040 * could be instructed to only run these tests by passing {@code -Dgroup="javac-test"} to Maven. 041 * 042 * <p>If your build is running in a GraalVM Native Image, then this test will not execute, as 043 * the <em>Java Compiler Testing</em> API is not yet tested within Native Images. 044 * 045 * <p>For example, to run a simple test on Java 11 through 17 (inclusive): 046 * 047 * <pre><code> 048 * class SomeTest { 049 * {@literal @JavacCompilerTest(minVersion = 11, maxVersion = 17)} 050 * void canCompileHelloWorld(JctCompiler> compiler) { 051 * // Given 052 * try (var workspace = Workspaces.newWorkspace()) { 053 * workspace 054 * .createFile("org", "example", "HelloWorld.java") 055 * .withContents(""" 056 * package org.example; 057 * 058 * public class HelloWorld { 059 * public static void main(String[] args) { 060 * System.out.println("Hello, World!"); 061 * } 062 * } 063 * """); 064 * 065 * var compilation = compiler.compile(workspace); 066 * 067 * assertThat(compilation) 068 * .isSuccessfulWithoutWarnings(); 069 * } 070 * } 071 * } 072 * </code></pre> 073 * 074 * @author Ashley Scopes 075 * @since 0.0.1 076 */ 077@API(since = "0.0.1", status = Status.STABLE) 078@ArgumentsSource(JavacCompilersProvider.class) 079@DisabledInNativeImage 080@Documented 081@ParameterizedTest(name = "for compiler \"{0}\"") 082@Retention(RetentionPolicy.RUNTIME) 083@Tags({ 084 @Tag("java-compiler-testing-test"), 085 @Tag("javac-test") 086}) 087@Target({ 088 ElementType.ANNOTATION_TYPE, 089 ElementType.METHOD, 090}) 091@TestTemplate 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 a 102 * range of JDKs during builds without needing to duplicate the test to satisfy different JDK 103 * 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 a 117 * range of JDKs during builds without needing to duplicate the test to satisfy different JDK 118 * 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 * <p>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 * The version strategy to use. 165 * 166 * <p>This determines whether the version number being iterated across specifies the 167 * release, source, target, or source and target versions. 168 * 169 * <p>The default is to specify the release. 170 * 171 * @return the version strategy to use. 172 */ 173 VersionStrategy versionStrategy() default VersionStrategy.RELEASE; 174}