package org.checkerframework.framework.test;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.annotation.processing.AbstractProcessor;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
 * Compiles all test files in a test directory together. Use {@link CheckerFrameworkPerFileTest} to
 * compile each test file in a test directory individually. A {@link
 * CheckerFrameworkPerDirectoryTest} is faster than an equivalent {@link
 * CheckerFrameworkPerFileTest}, but can only test that processor errors or warnings are issued.
 *
 * <p>To create a {@link CheckerFrameworkPerDirectoryTest}, create a new class that extends this
 * class. The new class must do the following:
 *
 * <ol>
 *   <li>Declare a constructor taking 1 parameter of type {@code java.util.List<java.io.File>}. This
 *       is a list of the files that will be compiled.
 *   <li>Declare the following method:
 *       <pre>{@code @Parameters public static String [] getTestDirs()}</pre>
 *       <p>getTestDir must return an array of directories that exist in the test folder. The
 *       directories can contain more path information (e.g., "myTestDir/moreTests") but note, the
 *       test suite will find all of the Java test files that exists below the listed directories.
 *       It is unnecessary to list child directories of a directory you have already listed.
 * </ol>
 *
 * <pre><code>
 * public class MyTest extends CheckerFrameworkPerDirectoryTest {
 *   /** @param testFiles the files containing test code, which will be type-checked *{@literal /}
 *   public MyTest(List{@literal <}File{@literal >} testFiles) {
 *     super(testFiles, MyChecker.class, "", "Anomsgtext");
 *   }
 *  {@literal @}Parameters
 *   public static String [] getTestDirs() {
 *     return new String[]{"all-systems"};
 *   }
 * }
 * </code></pre>
 */
@RunWith(PerDirectorySuite.class)
public abstract class CheckerFrameworkPerDirectoryTest {

    /** The files containing test code, which will be type-checked. */
    protected final List<File> testFiles;

    /** The fully-qualified class name of the checker to use for tests. */
    protected final String checkerName;

    /** The path, relative to currentDir/test to the directory containing test inputs. */
    protected final String testDir;

    /** Extra options to pass to javac when running the checker. */
    protected final List<String> checkerOptions;

    /**
     * Creates a new checker test.
     *
     * <p>{@link TestConfigurationBuilder#getDefaultConfigurationBuilder(String, File, String,
     * Iterable, Iterable, List, boolean)} adds additional checker options.
     *
     * @param testFiles the files containing test code, which will be type-checked
     * @param checker the class for the checker to use
     * @param testDir the path to the directory of test inputs
     * @param checkerOptions options to pass to the compiler when running tests
     */
    public CheckerFrameworkPerDirectoryTest(
            List<File> testFiles,
            Class<? extends AbstractProcessor> checker,
            String testDir,
            String... checkerOptions) {
        this.testFiles = testFiles;
        this.checkerName = checker.getName();
        this.testDir = "tests" + File.separator + testDir;
        this.checkerOptions = new ArrayList<>(Arrays.asList(checkerOptions));
    }

    @Test
    public void run() {
        boolean shouldEmitDebugInfo = TestUtilities.getShouldEmitDebugInfo();
        List<String> customizedOptions =
                customizeOptions(Collections.unmodifiableList(checkerOptions));
        TestConfiguration config =
                TestConfigurationBuilder.buildDefaultConfiguration(
                        testDir,
                        testFiles,
                        Collections.singleton(checkerName),
                        customizedOptions,
                        shouldEmitDebugInfo);
        TypecheckResult testResult = new TypecheckExecutor().runTest(config);
        TestUtilities.assertResultsAreValid(testResult);
    }

    /**
     * Override this method if you would like to supply a checker command-line option that depends
     * on the Java files passed to the test. Those files are available in field {@link #testFiles}.
     *
     * <p>If you want to specify the same command-line option for all tests of a particular checker,
     * then pass it to the {@link #CheckerFrameworkPerDirectoryTest} constructor.
     *
     * @param previousOptions the options specified in the constructor of the test previousOptions
     *     is unmodifiable
     * @return a new list of options or the original passed through
     */
    public List<String> customizeOptions(List<String> previousOptions) {
        return previousOptions;
    }
}
