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