package dotty.tools.dotc.vulpix;

import org.junit.BeforeClass;
import org.junit.AfterClass;
import java.util.ArrayDeque;
import java.util.function.Supplier;
import scala.Function0;
import scala.Unit;

import dotty.tools.dotc.reporting.TestReporter;

/** Note that while `ParallelTesting` runs in parallel, JUnit tests cannot with
 *  this class
 */
public class SummaryReport {
    public final static boolean isInteractive = !System.getenv().containsKey("DRONE");

    private static TestReporter rep = TestReporter.reporter(System.out, -1);
    private static ArrayDeque<String> failedTests = new ArrayDeque<>();
    private static ArrayDeque<String> reproduceInstructions = new ArrayDeque<>();
    private static int passed;
    private static int failed;

    public final static void reportFailed() {
        failed++;
    }

    public final static void reportPassed() {
        passed++;
    }

    public final static void addFailedTest(String msg) {
        failedTests.offer(msg);
    }

    public final static void addReproduceInstruction(String msg) {
        reproduceInstructions.offer(msg);
    }

    @BeforeClass public final static void setup() {
        rep = TestReporter.reporter(System.out, -1);
        failedTests = new ArrayDeque<>();
        reproduceInstructions = new ArrayDeque<>();
    }

    @AfterClass public final static void teardown() {
        rep.echo(
            "\n================================================================================" +
            "\nTest Report" +
            "\n================================================================================" +
            "\n" +
            passed + " passed, " + failed + " failed, " + (passed + failed) + " total" +
            "\n"
        );

        failedTests
            .stream()
            .map(x -> "    " + x)
            .forEach(rep::echo);

        // If we're compiling locally, we don't need reproduce instructions
        if (isInteractive) rep.flushToStdErr();

        rep.echo("");

        reproduceInstructions
            .stream()
            .forEach(rep::echo);

        // If we're on the CI, we want everything
        if (!isInteractive) rep.flushToStdErr();

        if (failed > 0) rep.flushToFile();
    }
}

@@ -683,32 +683,75 @@
 ArrayDeque%3C%3E();%0A
+    private static Supplier%3CVoid%3E cleanup;%0A
     private stat
@@ -1147,24 +1147,511 @@
 sg);%0A    %7D%0A%0A
+    public final static void addCleanup(Function0%3CUnit%3E func) %7B%0A        // Wow, look at how neatly we - compose cleanup callbacks:%0A        if (cleanup == null) %7B%0A            cleanup = () -%3E %7B%0A                func.apply();%0A                return null;%0A            %7D;%0A        %7D else %7B%0A            Supplier%3CVoid%3E oldCleanup = cleanup;%0A            cleanup = () -%3E %7B%0A                oldCleanup.get();%0A                func.apply();%0A                return null;%0A            %7D;%0A        %7D%0A    %7D%0A%0A
     @BeforeC
@@ -2767,16 +2767,98 @@
 oFile();
+%0A%0A        // Perform cleanup callback:%0A        if (cleanup != null) cleanup.get();
 %0A    %7D%0A%7D

Add cleanup hooks to SummaryReporter