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&lt;RuntimeException&gt; {
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}