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