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&lt;?, ?&gt;> 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&lt;RuntimeException&gt; {
143   *     {@literal @Override}
144   *     public void configure(JctCompiler&lt;?, ?&gt; compiler) {
145   *       compiler.failOnWarnings(true);
146   *     }
147   *   }
148   *
149   *   // ...
150   *
151   *   class SomeTest {
152   *     {@literal @JavacCompilerTest(configurers = WerrorConfigurer.class)}
153   *     void someTest(JctCompiler&lt;?, ?&gt; 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}