diff --git a/examples/quarkus-cli/src/test/java/io/quarkus/qe/QuarkusCliClientIT.java b/examples/quarkus-cli/src/test/java/io/quarkus/qe/QuarkusCliClientIT.java index a1d803f66..33433c6d8 100644 --- a/examples/quarkus-cli/src/test/java/io/quarkus/qe/QuarkusCliClientIT.java +++ b/examples/quarkus-cli/src/test/java/io/quarkus/qe/QuarkusCliClientIT.java @@ -1,12 +1,12 @@ package io.quarkus.qe; +import static io.quarkus.test.bootstrap.QuarkusCliClient.CreateApplicationRequest.defaults; import static io.quarkus.test.utils.AwaitilityUtils.untilAsserted; +import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.time.Duration; import java.util.List; -import java.util.concurrent.TimeUnit; import java.util.stream.Stream; import jakarta.inject.Inject; @@ -19,23 +19,34 @@ import io.quarkus.test.bootstrap.QuarkusCliDefaultService; import io.quarkus.test.bootstrap.QuarkusCliRestService; import io.quarkus.test.scenarios.QuarkusScenario; +import io.quarkus.test.scenarios.annotations.EnabledOnNative; +import io.quarkus.test.services.quarkus.model.QuarkusProperties; @Tag("quarkus-cli") @QuarkusScenario public class QuarkusCliClientIT { + static final String REST_SPRING_WEB_EXTENSION = "quarkus-spring-web"; static final String REST_EXTENSION = "quarkus-rest"; + static final String REST_JACKSON_EXTENSION = "quarkus-rest-jackson"; static final String SMALLRYE_HEALTH_EXTENSION = "quarkus-smallrye-health"; - static final int CMD_DELAY_SEC = 3; @Inject static QuarkusCliClient cliClient; + @Test + public void shouldVersionMatchQuarkusVersion() { + // Using option + assertEquals(QuarkusProperties.getVersion(), cliClient.run("--version").getOutput()); + + // Using shortcut + assertEquals(QuarkusProperties.getVersion(), cliClient.run("-v").getOutput()); + } + @Test public void shouldCreateApplicationOnJvm() { // Create application - QuarkusCliRestService app = cliClient.createApplication("app", - QuarkusCliClient.CreateApplicationRequest.defaults()); + QuarkusCliRestService app = cliClient.createApplication("app"); // Should build on Jvm QuarkusCliClient.Result result = app.buildOnJvm(); @@ -46,11 +57,36 @@ public void shouldCreateApplicationOnJvm() { app.given().get().then().statusCode(HttpStatus.SC_OK); } + @Test + @EnabledOnNative + public void shouldBuildApplicationOnNativeUsingDocker() { + // Create application + QuarkusCliRestService app = cliClient.createApplication("app"); + + // Should build on Native + QuarkusCliClient.Result result = app.buildOnNative(); + assertTrue(result.isSuccessful(), + "The application didn't build on Native. Output: " + result.getOutput()); + } + + @Test + public void shouldCreateApplicationWithCodeStarter() { + // Create application with Resteasy Jackson + QuarkusCliRestService app = cliClient.createApplication("app", defaults() + .withExtensions(REST_SPRING_WEB_EXTENSION, REST_JACKSON_EXTENSION)); + + // Verify By default, it installs only "quarkus-resteasy" + assertInstalledExtensions(app, REST_SPRING_WEB_EXTENSION, REST_JACKSON_EXTENSION); + + // Start using DEV mode + app.start(); + untilAsserted(() -> app.given().get("/greeting").then().statusCode(HttpStatus.SC_OK).and().body(is("Hello Spring"))); + } + @Test public void shouldCreateExtension() { // Create extension - QuarkusCliDefaultService app = cliClient.createExtension("extension-abc", - QuarkusCliClient.CreateExtensionRequest.defaults()); + QuarkusCliDefaultService app = cliClient.createExtension("extension-abc"); // Should build on Jvm QuarkusCliClient.Result result = app.buildOnJvm(); @@ -59,8 +95,7 @@ public void shouldCreateExtension() { @Test public void shouldCreateApplicationUsingArtifactId() { - QuarkusCliRestService app = cliClient.createApplication("com.mycompany:my-app", - QuarkusCliClient.CreateApplicationRequest.defaults()); + QuarkusCliRestService app = cliClient.createApplication("com.mycompany:my-app"); assertEquals("my-app", app.getServiceFolder().getFileName().toString(), "The application directory differs."); QuarkusCliClient.Result result = app.buildOnJvm(); @@ -68,10 +103,9 @@ public void shouldCreateApplicationUsingArtifactId() { } @Test - public void shouldAddAndRemoveExtensions() throws InterruptedException { + public void shouldAddAndRemoveExtensions() { // Create application - QuarkusCliRestService app = cliClient.createApplication("app", - QuarkusCliClient.CreateApplicationRequest.defaults()); + QuarkusCliRestService app = cliClient.createApplication("app"); // By default, it installs only "quarkus-rest" assertInstalledExtensions(app, REST_EXTENSION); @@ -93,19 +127,20 @@ public void shouldAddAndRemoveExtensions() throws InterruptedException { assertTrue(result.isSuccessful(), SMALLRYE_HEALTH_EXTENSION + " was not uninstalled. Output: " + result.getOutput()); // The health endpoint should be now gone - startAfter(app, Duration.ofSeconds(CMD_DELAY_SEC)); + app.start(); untilAsserted(() -> app.given().get("/q/health").then().statusCode(HttpStatus.SC_NOT_FOUND)); } + @Test + public void shouldListExtensionsUsingDefaults() { + var result = cliClient.listExtensions(); + assertTrue(result.getOutput().contains("quarkus-rest-jackson"), + "Listed extensions should contain quarkus-rest-jackson: " + result.getOutput()); + } + private void assertInstalledExtensions(QuarkusCliRestService app, String... expectedExtensions) { List extensions = app.getInstalledExtensions(); Stream.of(expectedExtensions).forEach(expectedExtension -> assertTrue(extensions.contains(expectedExtension), expectedExtension + " not found in " + extensions)); } - - // https://github.com/quarkusio/quarkus/issues/21070 - private void startAfter(QuarkusCliRestService app, Duration duration) throws InterruptedException { - TimeUnit.SECONDS.sleep(duration.getSeconds()); - app.start(); - } } diff --git a/quarkus-test-cli/src/main/java/io/quarkus/test/bootstrap/QuarkusCliClient.java b/quarkus-test-cli/src/main/java/io/quarkus/test/bootstrap/QuarkusCliClient.java index 0e8b951e2..a02858099 100644 --- a/quarkus-test-cli/src/main/java/io/quarkus/test/bootstrap/QuarkusCliClient.java +++ b/quarkus-test-cli/src/main/java/io/quarkus/test/bootstrap/QuarkusCliClient.java @@ -11,6 +11,8 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -27,6 +29,7 @@ public class QuarkusCliClient { public static final String COMMAND_LOG_FILE = "quarkus-cli-command.out"; public static final String DEV_MODE_LOG_FILE = "quarkus-cli-dev.out"; + private static final String QUARKUS_UPSTREAM_VERSION = "999-SNAPSHOT"; private static final String BUILD = "build"; private static final PropertyLookup COMMAND = new PropertyLookup("ts.quarkus.cli.cmd", "quarkus"); private static final Path TARGET = Paths.get("target"); @@ -69,12 +72,22 @@ public Process runOnDev(Path servicePath, File logOutput, Map ar return runCli(servicePath, logOutput, cmd.toArray(new String[cmd.size()])); } + public QuarkusCliRestService createApplicationAt(String name, String targetFolderName) { + Objects.requireNonNull(targetFolderName); + return createApplication(name, CreateApplicationRequest.defaults(), targetFolderName); + } + public QuarkusCliRestService createApplication(String name) { return createApplication(name, CreateApplicationRequest.defaults()); } public QuarkusCliRestService createApplication(String name, CreateApplicationRequest request) { - QuarkusCliRestService service = new QuarkusCliRestService(this); + return createApplication(name, request, null); + } + + public QuarkusCliRestService createApplication(String name, CreateApplicationRequest request, String targetFolderName) { + Path serviceFolder = isNotEmpty(targetFolderName) ? TARGET.resolve(targetFolderName).resolve(name) : null; + QuarkusCliRestService service = new QuarkusCliRestService(this, serviceFolder); ServiceContext serviceContext = service.register(name, context); service.init(s -> new CliDevModeLocalhostQuarkusApplicationManagedResource(serviceContext, this)); @@ -83,8 +96,7 @@ public QuarkusCliRestService createApplication(String name, CreateApplicationReq FileUtils.deletePath(serviceContext.getServiceFolder()); // Generate project - List args = new ArrayList<>(); - args.addAll(Arrays.asList("create", "app", name)); + List args = new ArrayList<>(Arrays.asList("create", "app", name)); // Platform Bom if (isNotEmpty(request.platformBom)) { args.add("--platform-bom=" + request.platformBom); @@ -189,6 +201,45 @@ private Process runCli(Path workingDirectory, File logOutput, String... args) { } } + private static boolean isUpstream() { + return isUpstream(QuarkusProperties.getVersion()); + } + + private static boolean isUpstream(String version) { + return version.contains(QUARKUS_UPSTREAM_VERSION); + } + + private static String getFixedStreamVersion() { + var rawVersion = QuarkusProperties.getVersion(); + if (isUpstream(rawVersion)) { + throw new IllegalStateException("Cannot set fixed stream version for '%s' as it doesn't exist" + rawVersion); + } + + String[] version = rawVersion.split(Pattern.quote(".")); + return String.format("%s.%s", version[0], version[1]); + } + + private static String getCurrentPlatformBom() { + return QuarkusProperties.PLATFORM_GROUP_ID.get() + "::" + QuarkusProperties.getVersion(); + } + + public Result listExtensions(String... extraArgs) { + return listExtensions(ListExtensionRequest.defaults(), extraArgs); + } + + public Result listExtensions(ListExtensionRequest request, String... extraArgs) { + List args = new ArrayList<>(); + args.add("extension"); + args.add("list"); + args.addAll(Arrays.asList(extraArgs)); + if (request.stream() != null) { + args.addAll(Arrays.asList("--stream", request.stream())); + } + var result = run(args.toArray(String[]::new)); + assertTrue(result.isSuccessful(), "Extensions list command didn't work. Output: " + result.getOutput()); + return result; + } + public static class CreateApplicationRequest { private String platformBom; private String stream; @@ -200,6 +251,10 @@ public CreateApplicationRequest withPlatformBom(String platformBom) { return this; } + public CreateApplicationRequest withCurrentPlatformBom() { + return withPlatformBom(getCurrentPlatformBom()); + } + public CreateApplicationRequest withStream(String stream) { this.stream = stream; return this; @@ -216,10 +271,12 @@ public CreateApplicationRequest withExtraArgs(String... extraArgs) { } public static CreateApplicationRequest defaults() { - if (QuarkusProperties.getVersion().contains("SNAPSHOT")) { - return new CreateApplicationRequest().withPlatformBom("io.quarkus::" + QuarkusProperties.getVersion()); + if (isUpstream()) { + // set platform due to https://github.com/quarkusio/quarkus/issues/40951#issuecomment-2147399201 + return new CreateApplicationRequest().withCurrentPlatformBom(); } - return new CreateApplicationRequest(); + // set fixed stream because if tested stream is not the latest stream, we would create app with wrong version + return new CreateApplicationRequest().withStream(getFixedStreamVersion()); } } @@ -228,6 +285,10 @@ public static class CreateExtensionRequest { private String stream; private String[] extraArgs; + public CreateExtensionRequest withCurrentPlatformBom() { + return withPlatformBom(getCurrentPlatformBom()); + } + public CreateExtensionRequest withPlatformBom(String platformBom) { this.platformBom = platformBom; return this; @@ -244,7 +305,11 @@ public CreateExtensionRequest withExtraArgs(String... extraArgs) { } public static CreateExtensionRequest defaults() { - return new CreateExtensionRequest(); + if (isUpstream()) { + return new CreateExtensionRequest(); + } + // set fixed stream because if tested stream is not the latest stream, we would create app with wrong version + return new CreateExtensionRequest().withStream(getFixedStreamVersion()); } } @@ -267,4 +332,14 @@ public boolean isSuccessful() { return EXIT_SUCCESS == exitCode; } } + + public record ListExtensionRequest(String stream) { + public static ListExtensionRequest defaults() { + return new ListExtensionRequest(isUpstream() ? null : getFixedStreamVersion()); + } + + public static ListExtensionRequest withSetStream() { + return new ListExtensionRequest(isUpstream() ? QUARKUS_UPSTREAM_VERSION : getFixedStreamVersion()); + } + } } diff --git a/quarkus-test-cli/src/main/java/io/quarkus/test/bootstrap/QuarkusCliRestService.java b/quarkus-test-cli/src/main/java/io/quarkus/test/bootstrap/QuarkusCliRestService.java index 2096dcfde..dc49922b2 100644 --- a/quarkus-test-cli/src/main/java/io/quarkus/test/bootstrap/QuarkusCliRestService.java +++ b/quarkus-test-cli/src/main/java/io/quarkus/test/bootstrap/QuarkusCliRestService.java @@ -2,15 +2,21 @@ import static org.junit.jupiter.api.Assertions.assertTrue; +import java.io.File; +import java.nio.file.Path; +import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; public class QuarkusCliRestService extends RestService { private final QuarkusCliClient cliClient; + private final Path serviceFolder; - public QuarkusCliRestService(QuarkusCliClient cliClient) { + public QuarkusCliRestService(QuarkusCliClient cliClient, Path serviceFolder) { this.cliClient = cliClient; + this.serviceFolder = serviceFolder; } public QuarkusCliClient.Result buildOnJvm(String... extraArgs) { @@ -36,4 +42,28 @@ public List getInstalledExtensions() { .map(line -> line.replace("✬ ", "")).collect(Collectors.toList()); } + public File getFileFromApplication(String fileName) { + // get file from the service folder + return getFileFromApplication("", fileName); + } + + public File getFileFromApplication(String subFolder, String fileName) { + Path fileFolderPath = getServiceFolder(); + if (subFolder != null && !subFolder.isEmpty()) { + fileFolderPath = Path.of(fileFolderPath.toString(), subFolder); + } + + return Arrays.stream(Objects.requireNonNull(fileFolderPath.toFile().listFiles())) + .filter(f -> f.getName().equalsIgnoreCase(fileName)) + .findFirst() + .orElseThrow(() -> new RuntimeException(fileName + " not found.")); + } + + @Override + protected ServiceContext createServiceContext(ScenarioContext context) { + if (serviceFolder != null) { + return new ServiceContext(this, context, serviceFolder); + } + return super.createServiceContext(context); + } } diff --git a/quarkus-test-cli/src/test/java/io/quarkus/test/QuarkusCliClientIT.java b/quarkus-test-cli/src/test/java/io/quarkus/test/QuarkusCliClientIT.java deleted file mode 100644 index 4c77f993d..000000000 --- a/quarkus-test-cli/src/test/java/io/quarkus/test/QuarkusCliClientIT.java +++ /dev/null @@ -1,137 +0,0 @@ -package io.quarkus.test; - -import static io.quarkus.test.bootstrap.QuarkusCliClient.CreateApplicationRequest.defaults; -import static io.quarkus.test.utils.AwaitilityUtils.untilAsserted; -import static org.hamcrest.Matchers.is; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.time.Duration; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.stream.Stream; - -import jakarta.inject.Inject; - -import org.apache.http.HttpStatus; -import org.junit.jupiter.api.Tag; -import org.junit.jupiter.api.Test; - -import io.quarkus.test.bootstrap.QuarkusCliClient; -import io.quarkus.test.bootstrap.QuarkusCliRestService; -import io.quarkus.test.scenarios.QuarkusScenario; -import io.quarkus.test.scenarios.annotations.EnabledOnNative; -import io.quarkus.test.services.quarkus.model.QuarkusProperties; - -@Tag("quarkus-cli") -@QuarkusScenario -public class QuarkusCliClientIT { - - static final String REST_SPRING_WEB_EXTENSION = "quarkus-spring-web"; - static final String REST_EXTENSION = "quarkus-rest"; - static final String REST_JACKSON_EXTENSION = "quarkus-rest-jackson"; - static final String SMALLRYE_HEALTH_EXTENSION = "quarkus-smallrye-health"; - static final int CMD_DELAY_SEC = 3; - - @Inject - static QuarkusCliClient cliClient; - - @Test - public void shouldVersionMatchQuarkusVersion() { - // Using option - assertEquals(QuarkusProperties.getVersion(), cliClient.run("--version").getOutput()); - - // Using shortcut - assertEquals(QuarkusProperties.getVersion(), cliClient.run("-v").getOutput()); - } - - @Test - public void shouldCreateApplicationOnJvm() { - // Create application - QuarkusCliRestService app = cliClient.createApplication("app", defaults()); - - // Should build on Jvm - QuarkusCliClient.Result result = app.buildOnJvm(); - assertTrue(result.isSuccessful(), "The application didn't build on JVM. Output: " + result.getOutput()); - - // Start using DEV mode - app.start(); - app.given().get().then().statusCode(HttpStatus.SC_OK); - } - - @Test - @EnabledOnNative - public void shouldBuildApplicationOnNativeUsingDocker() { - // Create application - QuarkusCliRestService app = cliClient.createApplication("app", defaults()); - - // Should build on Native - QuarkusCliClient.Result result = app.buildOnNative(); - assertTrue(result.isSuccessful(), "The application didn't build on Native. Output: " + result.getOutput()); - } - - @Test - public void shouldCreateApplicationWithCodeStarter() { - // Create application with Resteasy Jackson - QuarkusCliRestService app = cliClient.createApplication("app", - defaults().withExtensions(REST_SPRING_WEB_EXTENSION, - REST_JACKSON_EXTENSION)); - - // Verify By default, it installs only "quarkus-resteasy" - assertInstalledExtensions(app, REST_SPRING_WEB_EXTENSION, REST_JACKSON_EXTENSION); - - // Start using DEV mode - app.start(); - untilAsserted(() -> app.given().get("/greeting").then().statusCode(HttpStatus.SC_OK).and().body(is("Hello Spring"))); - } - - @Test - public void shouldCreateApplicationUsingArtifactId() { - QuarkusCliRestService app = cliClient.createApplication("com.mycompany:my-app", defaults()); - assertEquals("my-app", app.getServiceFolder().getFileName().toString(), "The application directory differs."); - - QuarkusCliClient.Result result = app.buildOnJvm(); - assertTrue(result.isSuccessful(), "The application didn't build on JVM. Output: " + result.getOutput()); - } - - @Test - public void shouldAddAndRemoveExtensions() throws InterruptedException { - // Create application - QuarkusCliRestService app = cliClient.createApplication("app", defaults()); - - // By default, it installs only "quarkus-rest" - assertInstalledExtensions(app, REST_EXTENSION); - - // Let's install Quarkus SmallRye Health - QuarkusCliClient.Result result = app.installExtension(SMALLRYE_HEALTH_EXTENSION); - assertTrue(result.isSuccessful(), SMALLRYE_HEALTH_EXTENSION + " was not installed. Output: " + result.getOutput()); - - // Verify both extensions now - assertInstalledExtensions(app, REST_EXTENSION, SMALLRYE_HEALTH_EXTENSION); - - // The health endpoint should be now available - app.start(); - untilAsserted(() -> app.given().get("/q/health").then().statusCode(HttpStatus.SC_OK)); - app.stop(); - - // Let's now remove the SmallRye Health extension - result = app.removeExtension(SMALLRYE_HEALTH_EXTENSION); - assertTrue(result.isSuccessful(), SMALLRYE_HEALTH_EXTENSION + " was not uninstalled. Output: " + result.getOutput()); - - // The health endpoint should be now gone - startAfter(app, Duration.ofSeconds(CMD_DELAY_SEC)); - untilAsserted(() -> app.given().get("/q/health").then().statusCode(HttpStatus.SC_NOT_FOUND)); - } - - private void assertInstalledExtensions(QuarkusCliRestService app, String... expectedExtensions) { - List extensions = app.getInstalledExtensions(); - Stream.of(expectedExtensions).forEach(expectedExtension -> assertTrue(extensions.contains(expectedExtension), - expectedExtension + " not found in " + extensions)); - } - - // https://github.com/quarkusio/quarkus/issues/21070 - private void startAfter(QuarkusCliRestService app, Duration duration) throws InterruptedException { - TimeUnit.SECONDS.sleep(duration.getSeconds()); - app.start(); - } -} diff --git a/quarkus-test-cli/src/test/resources/test.properties b/quarkus-test-cli/src/test/resources/test.properties deleted file mode 100644 index a4414abc6..000000000 --- a/quarkus-test-cli/src/test/resources/test.properties +++ /dev/null @@ -1,2 +0,0 @@ -ts.app.log.enable=true -ts.global.generated-service.enabled=false diff --git a/quarkus-test-core/src/main/java/io/quarkus/test/bootstrap/BaseService.java b/quarkus-test-core/src/main/java/io/quarkus/test/bootstrap/BaseService.java index d20eadc95..85ac85046 100644 --- a/quarkus-test-core/src/main/java/io/quarkus/test/bootstrap/BaseService.java +++ b/quarkus-test-core/src/main/java/io/quarkus/test/bootstrap/BaseService.java @@ -275,7 +275,7 @@ private ServiceContext registerWithSanitizedServiceName(ScenarioContext context, this.configuration = Configuration.load(serviceName, originalServiceName); } - this.context = new ServiceContext(this, context); + this.context = createServiceContext(context); onPreStart(s -> properties.putAll(this.context.getConfigPropertiesWithTestScope())); onPreStop(s -> this.context.getConfigPropertiesWithTestScope().forEach((k, v) -> properties.remove(k))); @@ -284,6 +284,10 @@ private ServiceContext registerWithSanitizedServiceName(ScenarioContext context, return this.context; } + protected ServiceContext createServiceContext(ScenarioContext context) { + return new ServiceContext(this, context); + } + @Override public void init(ManagedResourceBuilder managedResourceBuilder) { FileUtils.recreateDirectory(context.getServiceFolder()); diff --git a/quarkus-test-core/src/main/java/io/quarkus/test/bootstrap/ServiceContext.java b/quarkus-test-core/src/main/java/io/quarkus/test/bootstrap/ServiceContext.java index d2031df6b..6972ac6eb 100644 --- a/quarkus-test-core/src/main/java/io/quarkus/test/bootstrap/ServiceContext.java +++ b/quarkus-test-core/src/main/java/io/quarkus/test/bootstrap/ServiceContext.java @@ -3,6 +3,7 @@ import java.nio.file.Path; import java.util.HashMap; import java.util.Map; +import java.util.Objects; public final class ServiceContext { @@ -22,6 +23,12 @@ public final class ServiceContext { */ private final Map configPropertiesWithTestScope = new HashMap<>(); + ServiceContext(Service owner, ScenarioContext scenarioContext, Path serviceFolder) { + this.owner = owner; + this.scenarioContext = scenarioContext; + this.serviceFolder = Objects.requireNonNull(serviceFolder); + } + ServiceContext(Service owner, ScenarioContext scenarioContext) { this.owner = owner; this.scenarioContext = scenarioContext;