Skip to content

Commit

Permalink
Merge pull request #30281 from holly-cummins/enable-quarkus-test-testing
Browse files Browse the repository at this point in the history
  • Loading branch information
aloubyansky authored Jan 10, 2023
2 parents f897508 + e7a4ff7 commit 068aebe
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
Expand Down Expand Up @@ -47,14 +46,21 @@
import io.restassured.RestAssured;

/**
* Tests the quarkus:test mojo.
*
* @author <a href="http://escoffier.me">Clement Escoffier</a>
* <p>
* NOTE to anyone diagnosing failures in this test, to run a single method use:
* <p>
* mvn install -Dit.test=DevMojoIT#methodName
*/
@DisableForNative
public class DevMojoIT extends RunAndCheckMojoTestBase {
public class DevMojoIT extends LaunchMojoTestBase {

@Override
protected ContinuousTestingMavenTestUtils getTestingTestUtils() {
return new ContinuousTestingMavenTestUtils();
}

@Test
public void testConfigFactoryInAppModuleBannedInCodeGen() throws MavenInvocationException, IOException {
Expand Down Expand Up @@ -98,7 +104,7 @@ public void testEnvironmentVariablesConfig() throws MavenInvocationException, IO

@Test
void testClassLoaderLinkageError()
throws MavenInvocationException, IOException, InterruptedException {
throws MavenInvocationException, IOException {
testDir = initProject("projects/classloader-linkage-error", "projects/classloader-linkage-error-dev");
run(true);
assertThat(DevModeTestUtils.getHttpResponse("/hello")).isEqualTo("hello");
Expand Down Expand Up @@ -179,7 +185,7 @@ public void testCommandModeAppSystemPropArguments() throws MavenInvocationExcept
final File done = new File(testDir, "done.txt");
await()
.pollDelay(1, TimeUnit.SECONDS)
.atMost(20, TimeUnit.MINUTES).until(() -> done.exists());
.atMost(20, TimeUnit.MINUTES).until(done::exists);

// read the log and check the passed in args
final File log = new File(testDir, "build-command-mode-app-args.log");
Expand All @@ -197,7 +203,7 @@ public void testCommandModeAppPomConfigArguments() throws MavenInvocationExcepti
final File done = new File(testDir, "done.txt");
await()
.pollDelay(1, TimeUnit.SECONDS)
.atMost(20, TimeUnit.MINUTES).until(() -> done.exists());
.atMost(20, TimeUnit.MINUTES).until(done::exists);

// read the log and check the passed in args
final File log = new File(testDir, "build-command-mode-app-pom-args.log");
Expand All @@ -206,7 +212,7 @@ public void testCommandModeAppPomConfigArguments() throws MavenInvocationExcepti
assertThat(loggedArgs).isEqualTo("ARGS: [plugin, pom, config]");
}

private String extractLoggedArgs(final File log) throws IOException, FileNotFoundException {
private String extractLoggedArgs(final File log) throws IOException {
String loggedArgs = null;
try (BufferedReader reader = new BufferedReader(new FileReader(log))) {
String s;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package io.quarkus.maven.it;

import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;

import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import org.apache.maven.shared.invoker.MavenInvocationException;
import org.junit.jupiter.api.Test;

import io.quarkus.maven.it.continuoustesting.ContinuousTestingMavenTestUtils;

/**
* Contains tests that we expect to pass with both quarkus:dev and quarkus:test
*/
@DisableForNative
public abstract class LaunchMojoTestBase extends RunAndCheckMojoTestBase {

protected abstract ContinuousTestingMavenTestUtils getTestingTestUtils();

@Test
public void testThatTheTestsAreReRunMultiModule()
throws MavenInvocationException, IOException {
//we also check continuous testing
testDir = initProject("projects/multimodule", "projects/multimodule-with-deps");
runAndCheck();

ContinuousTestingMavenTestUtils testingTestUtils = getTestingTestUtils();
ContinuousTestingMavenTestUtils.TestStatus results = testingTestUtils.waitForNextCompletion();

//check that the tests in both modules run
assertEquals(2, results.getTestsPassed());

// Edit the "Hello" message.
File source = new File(testDir, "rest/src/main/java/org/acme/HelloResource.java");
final String uuid = UUID.randomUUID().toString();
filter(source, Collections.singletonMap("return \"hello\";", "return \"" + uuid + "\";"));

// Wait until we get "uuid"
// We can't poll, so just pause
try {
Thread.sleep(2 * 1000);
} catch (InterruptedException e) {
fail(e);
}
await()
.pollDelay(100, TimeUnit.MILLISECONDS)
.pollInterval(1, TimeUnit.SECONDS)
.until(source::isFile);

results = testingTestUtils.waitForNextCompletion();

//make sure the test is failing now
assertEquals(1, results.getTestsFailed());
//now modify the passing test
var testSource = new File(testDir, "rest/src/test/java/org/acme/test/SimpleTest.java");
filter(testSource, Collections.singletonMap("Assertions.assertTrue(true);", "Assertions.assertTrue(false);"));
results = testingTestUtils.waitForNextCompletion();
assertEquals(2, results.getTotalTestsFailed());
//fix it again
filter(testSource, Collections.singletonMap("Assertions.assertTrue(false);", "Assertions.assertTrue(true);"));
results = testingTestUtils.waitForNextCompletion();
assertEquals(1, results.getTotalTestsFailed(), "Failed, actual results " + results);
assertEquals(1, results.getTotalTestsPassed(), "Failed, actual results " + results);

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.quarkus.maven.it;

import java.io.FileNotFoundException;

import org.apache.maven.shared.invoker.MavenInvocationException;

import io.quarkus.maven.it.continuoustesting.ContinuousTestingMavenTestUtils;
import io.quarkus.maven.it.continuoustesting.TestModeContinuousTestingMavenTestUtils;
import io.quarkus.runtime.LaunchMode;

/**
* Tests the quarkus:test mojo. Most of the behaviour of quarkus:test is expected to also work with quarkus:test, so tests are
* in a superclass.
* <p>
* NOTE to anyone diagnosing failures in this test, to run a single method use:
* <p>
* mvn install -Dit.test=TestMojoIT#methodName
*/
@DisableForNative
public class TestMojoIT extends LaunchMojoTestBase {

@Override
protected LaunchMode getDefaultLaunchMode() {
return LaunchMode.TEST;
}

@Override
protected ContinuousTestingMavenTestUtils getTestingTestUtils() {
return new TestModeContinuousTestingMavenTestUtils(running);
}

@Override
public void shutdownTheApp() {
if (running != null) {
running.stop();
}

// There's no http server, so there's nothing to check to make sure we're stopped, except by the maven invoker itself, or the logs
}

/**
* This is actually more like runAndDoNotCheck, because
* we can't really check anything via a HTTP get, because this is a test mode application
*/
@Override
protected void runAndCheck(boolean performCompile, LaunchMode mode, String... options)
throws FileNotFoundException, MavenInvocationException {
run(performCompile, mode, options);

// We don't need to try and pause, because the continuous testing utils will wait for tests to finish

}

}
4 changes: 4 additions & 0 deletions test-framework/maven/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devtools-testing</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-devmode-test-utils</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
Expand All @@ -17,6 +16,7 @@

import io.quarkus.maven.it.verifier.MavenProcessInvocationResult;
import io.quarkus.maven.it.verifier.RunningInvoker;
import io.quarkus.runtime.LaunchMode;
import io.quarkus.test.devmode.util.DevModeTestUtils;

public class RunAndCheckMojoTestBase extends MojoTestBase {
Expand All @@ -25,7 +25,7 @@ public class RunAndCheckMojoTestBase extends MojoTestBase {
protected File testDir;

@AfterEach
public void cleanup() throws IOException {
public void cleanup() {
shutdownTheApp();
}

Expand All @@ -36,14 +36,29 @@ public void shutdownTheApp() {
DevModeTestUtils.awaitUntilServerDown();
}

/**
* Quarkus can be launched as `quarkus:dev` or `quarkus:test`.
* In most cases it doesn't matter and dev mode is fine, but sometimes it's useful to cover test mode,
* since it sometimes behaves differently.
*/
protected LaunchMode getDefaultLaunchMode() {
return LaunchMode.DEVELOPMENT;
}

protected void run(boolean performCompile, String... options) throws FileNotFoundException, MavenInvocationException {
run(performCompile, getDefaultLaunchMode(), options);
}

protected void run(boolean performCompile, LaunchMode mode, String... options)
throws FileNotFoundException, MavenInvocationException {
assertThat(testDir).isDirectory();
running = new RunningInvoker(testDir, false);

final List<String> args = new ArrayList<>(2 + options.length);
if (performCompile) {
args.add("compile");
}
args.add("quarkus:dev");
args.add("quarkus:" + mode.getDefaultProfile());
boolean hasDebugOptions = false;
for (String option : options) {
args.add(option);
Expand All @@ -68,9 +83,18 @@ protected void runAndCheck(String... options) throws FileNotFoundException, Mave
runAndCheck(true, options);
}

protected void runAndCheck(LaunchMode mode, String... options) throws FileNotFoundException, MavenInvocationException {
runAndCheck(true, mode, options);
}

protected void runAndCheck(boolean performCompile, String... options)
throws MavenInvocationException, FileNotFoundException {
runAndCheck(performCompile, getDefaultLaunchMode(), options);
}

protected void runAndCheck(boolean performCompile, LaunchMode mode, String... options)
throws FileNotFoundException, MavenInvocationException {
run(performCompile, options);
run(performCompile, mode, options);

String resp = DevModeTestUtils.getHttpResponse();

Expand All @@ -81,7 +105,7 @@ protected void runAndCheck(boolean performCompile, String... options)
assertThat(greeting).containsIgnoringCase("hello");
}

protected void runAndExpectError() throws FileNotFoundException, MavenInvocationException {
protected void runAndExpectError() throws MavenInvocationException {
assertThat(testDir).isDirectory();
running = new RunningInvoker(testDir, false);
final Properties mvnRunProps = new Properties();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package io.quarkus.maven.it.continuoustesting;

import static org.awaitility.Awaitility.await;
import static org.junit.jupiter.api.Assertions.fail;

import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import io.quarkus.maven.it.verifier.RunningInvoker;

/**
* Utilities for testing behaviour with `mvn quarkus:test`. This is harder than dev mode, since
* we don't have an http endpoint to query for test status, but we can do our best effort.
*/
public class TestModeContinuousTestingMavenTestUtils extends ContinuousTestingMavenTestUtils {
// Example output we look for
// 1 test failed (1 passing, 0 skipped), 1 test was run in 217ms. Tests completed at 21:22:34 due to changes to HelloResource$Blah.class and 1 other files.
// All 2 tests are passing (0 skipped), 2 tests were run in 1413ms. Tests completed at 21:22:33.
private static final Pattern ALL_PASSING = Pattern.compile("All (\\d\\d*) tests are passing \\((\\d\\d*) skipped\\)",
Pattern.MULTILINE);
private static final Pattern SOME_PASSING = Pattern
.compile("(\\d\\d*) tests? failed \\((\\d\\d*) passing, (\\d\\d*) skipped\\)", Pattern.MULTILINE);
private static final String TESTS_COMPLETED = "Tests completed at";
private final RunningInvoker running;
private int startPosition = 0;

public TestModeContinuousTestingMavenTestUtils(RunningInvoker running) {
this.running = running;
}

@Override
public TestStatus waitForNextCompletion() {

// We have to scrape test status, because in test mode we do not have an API
await()
.pollDelay(1, TimeUnit.SECONDS)
.atMost(3, TimeUnit.MINUTES).until(() -> getLogSinceLastRun().contains(TESTS_COMPLETED));
TestStatus testStatus = new TestStatus();
try {
String log = getLogSinceLastRun();

Matcher matcher = ALL_PASSING.matcher(log);
int failCount;
int passCount;
int skipCount;
if (matcher.find()) {
passCount = Integer.parseInt(matcher.group(1));
skipCount = Integer.parseInt(matcher.group(2));
failCount = 0;
} else {
matcher = SOME_PASSING.matcher(log);
if (!matcher.find()) {
fail("Tests were run, but the log is not parseable with the patterns we know. This is the log\n: " + log);
}
failCount = Integer.parseInt(matcher.group(1));
passCount = Integer.parseInt(matcher.group(2));
skipCount = Integer.parseInt(matcher.group(3));
}
testStatus.setTestsFailed(failCount);
testStatus.setTestsPassed(passCount);
testStatus.setTestsSkipped(skipCount);

// Note: slight fudging of total counts!
// io.quarkus.test.ContinuousTestingTestUtils treats the total counts the same as the current counts,
// so we will do the same.
// it's not ideal, so if it causes problems we may want to invest in more elaborate parsing
testStatus.setTotalTestsFailed(failCount);
testStatus.setTotalTestsPassed(passCount);
testStatus.setTotalTestsSkipped(skipCount);

setHighWaterMark();
} catch (IOException e) {
fail(e);
}
return testStatus;

}

private void setHighWaterMark() throws IOException {
// We only want to check the logs in the section which was updated after the last completion,
// so make a note of what the position was
startPosition = startPosition + getLogSinceLastRun().indexOf(TESTS_COMPLETED) + TESTS_COMPLETED.length();
}

private String getLogSinceLastRun() throws IOException {
String log = running.log();
return log.substring(startPosition);

}
}

0 comments on commit 068aebe

Please sign in to comment.