From 65e579966cd2f72813a661d356f7af4209dbe6cc Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Sun, 5 Mar 2023 11:22:28 +0100 Subject: [PATCH 1/2] Revert `RunTask` --- app/build.gradle.kts | 11 ++-- app/src/main/templates/hello.html | 2 +- build-logic/build.gradle.kts | 7 --- .../com/uwyn/rife2/gradle/Rife2Extension.java | 3 -- .../com/uwyn/rife2/gradle/Rife2Plugin.java | 40 +++++--------- .../java/com/uwyn/rife2/gradle/RunTask.java | 54 ------------------- war/build.gradle.kts | 5 +- 7 files changed, 23 insertions(+), 99 deletions(-) delete mode 100644 build-logic/src/main/java/com/uwyn/rife2/gradle/RunTask.java diff --git a/app/build.gradle.kts b/app/build.gradle.kts index de722df..07daedc 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -3,15 +3,19 @@ import org.gradle.api.tasks.testing.logging.TestLogEvent import com.uwyn.rife2.gradle.TemplateType.* plugins { + application id("com.uwyn.rife2") `maven-publish` } -version = 1.0 -group = "com.example" - base { archivesName.set("hello") + version = 1.0 + group = "com.example" +} + +application { + mainClass.set("hello.App") } java { @@ -27,7 +31,6 @@ repositories { } rife2 { - mainClass.set("hello.App") version.set("1.4.0") useAgent.set(true) precompiledTemplateTypes.addAll(HTML) diff --git a/app/src/main/templates/hello.html b/app/src/main/templates/hello.html index f7ffe8b..59ff81b 100644 --- a/app/src/main/templates/hello.html +++ b/app/src/main/templates/hello.html @@ -8,4 +8,4 @@

Hello World

- \ No newline at end of file + diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index b9ea647..b29e98f 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -10,13 +10,6 @@ dependencies { gradleApi() } -tasks { - withType { - options.isDeprecation = true - options.compilerArgs.add("-Xlint:unchecked") - } -} - gradlePlugin { plugins { create("rife2") { diff --git a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Extension.java b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Extension.java index 23a4ece..30fa218 100644 --- a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Extension.java +++ b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Extension.java @@ -18,10 +18,7 @@ package com.uwyn.rife2.gradle; import org.gradle.api.provider.ListProperty; import org.gradle.api.provider.Property; -@SuppressWarnings("unused") public abstract class Rife2Extension { - public abstract Property getMainClass(); - public abstract Property getVersion(); public abstract Property getUseAgent(); diff --git a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java index 9409eea..9a1b5fe 100644 --- a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java +++ b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java @@ -25,10 +25,14 @@ import org.gradle.api.component.AdhocComponentWithVariants; import org.gradle.api.component.ConfigurationVariantDetails; import org.gradle.api.file.DuplicatesStrategy; import org.gradle.api.plugins.BasePluginExtension; +import org.gradle.api.plugins.JavaApplication; import org.gradle.api.plugins.JavaPlugin; import org.gradle.api.plugins.JavaPluginExtension; import org.gradle.api.plugins.PluginContainer; -import org.gradle.api.tasks.*; +import org.gradle.api.tasks.JavaExec; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskContainer; +import org.gradle.api.tasks.TaskProvider; import org.gradle.api.tasks.bundling.Jar; import org.gradle.api.tasks.testing.Test; import org.gradle.process.CommandLineArgumentProvider; @@ -36,7 +40,6 @@ import org.gradle.process.CommandLineArgumentProvider; import java.util.Collections; import java.util.Locale; -@SuppressWarnings({"ALL", "unused"}) public class Rife2Plugin implements Plugin { public static final String DEFAULT_TEMPLATES_DIR = "src/main/templates"; public static final String DEFAULT_GENERATED_RIFE2_CLASSES_DIR = "generated/classes/rife2"; @@ -62,9 +65,7 @@ public class Rife2Plugin implements Plugin { createRife2DevelopmentOnlyConfiguration(project, configurations, dependencyHandler, precompileTemplates); exposePrecompiledTemplatesToTestTask(project, configurations, dependencyHandler, precompileTemplates); configureAgent(project, plugins, rife2Extension, rife2AgentClasspath); - registerRunTask(project, rife2Extension, rife2AgentClasspath); - var uberJarTask = registerUberJarTask(project, plugins, javaPluginExtension, rife2Extension, tasks, precompileTemplates); - + TaskProvider uberJarTask = registerUberJarTask(project, plugins, javaPluginExtension, rife2Extension, tasks, precompileTemplates); bundlePrecompiledTemplatesIntoJarFile(tasks, precompileTemplates); configureMavenPublishing(project, plugins, configurations, uberJarTask); @@ -86,13 +87,11 @@ public class Rife2Plugin implements Plugin { conf.attributes(attrs -> { for (Attribute attribute : runtimeAttributes.keySet()) { Object value = runtimeAttributes.getAttribute(attribute); - if (value != null) { - if (Bundling.class.equals(attribute.getType())) { - attrs.attribute(Bundling.BUNDLING_ATTRIBUTE, project.getObjects().named(Bundling.class, Bundling.SHADOWED)); - } else { - //noinspection unchecked - attrs.attribute((Attribute) attribute, value); - } + //noinspection unchecked + if (Bundling.class.equals(attribute.getType())) { + attrs.attribute(Bundling.BUNDLING_ATTRIBUTE, project.getObjects().named(Bundling.class, Bundling.SHADOWED)); + } else { + attrs.attribute((Attribute) attribute, value); } } }); @@ -173,7 +172,8 @@ public class Rife2Plugin implements Plugin { private static Rife2Extension createRife2Extension(Project project) { var rife2 = project.getExtensions().create("rife2", Rife2Extension.class); rife2.getUseAgent().convention(false); - rife2.getUberMainClass().set(rife2.getMainClass() + "Uber"); + rife2.getUberMainClass().convention(project.getExtensions().getByType(JavaApplication.class).getMainClass() + .map(mainClass -> mainClass + "Uber")); return rife2; } @@ -226,18 +226,4 @@ public class Rife2Plugin implements Plugin { task.getOutputDirectory().set(project.getLayout().getBuildDirectory().dir(DEFAULT_GENERATED_RIFE2_CLASSES_DIR)); }); } - - private static void registerRunTask(Project project, - Rife2Extension rife2Extension, - Configuration rife2CompilerClasspath) { - project.getTasks().register("run", RunTask.class, task -> { - task.setGroup(RIFE2_GROUP); - task.setDescription("Runs this project as a web application."); - task.getAgentClassPath().set(rife2CompilerClasspath.getAsPath()); - task.getClasspath().from(project.getExtensions().getByType(SourceSetContainer.class) - .getByName(SourceSet.MAIN_SOURCE_SET_NAME).getRuntimeClasspath()); - task.getMainClass().set(rife2Extension.getMainClass()); - task.getTemplatesDirectory().set(project.getLayout().getProjectDirectory().dir(DEFAULT_TEMPLATES_DIR)); - }); - } } diff --git a/build-logic/src/main/java/com/uwyn/rife2/gradle/RunTask.java b/build-logic/src/main/java/com/uwyn/rife2/gradle/RunTask.java deleted file mode 100644 index c107544..0000000 --- a/build-logic/src/main/java/com/uwyn/rife2/gradle/RunTask.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 2003-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.uwyn.rife2.gradle; - -import org.gradle.api.DefaultTask; -import org.gradle.api.file.ConfigurableFileCollection; -import org.gradle.api.file.DirectoryProperty; -import org.gradle.api.provider.Property; -import org.gradle.api.tasks.*; -import org.gradle.process.ExecOperations; - -import javax.inject.Inject; -import java.util.List; - -@CacheableTask -public abstract class RunTask extends DefaultTask { - @Input - public abstract Property getAgentClassPath(); - - @Classpath - public abstract ConfigurableFileCollection getClasspath(); - - @Inject - protected abstract ExecOperations getExecOperations(); - - @Input - public abstract Property getMainClass(); - - @InputDirectory - @PathSensitive(PathSensitivity.RELATIVE) - public abstract DirectoryProperty getTemplatesDirectory(); - - @TaskAction - public void run() { - getExecOperations().javaexec(run -> { - run.setClasspath(getProject().getObjects().fileCollection().from(getTemplatesDirectory()).plus(getClasspath())); - run.getMainClass().set(getMainClass()); - run.args(List.of("-javaagent:" + getAgentClassPath().get())); - }); - } -} \ No newline at end of file diff --git a/war/build.gradle.kts b/war/build.gradle.kts index 477784d..206f003 100644 --- a/war/build.gradle.kts +++ b/war/build.gradle.kts @@ -2,10 +2,9 @@ plugins { war } -version = 1.0 - base { archivesName.set("hello") + version = 1.0 } repositories { @@ -21,4 +20,4 @@ tasks.war { webAppDirectory.set(file("../app/src/main/webapp")) webXml = file("src/web.xml") rootSpec.exclude("**/jetty*.jar", "**/slf4j*.jar", "**/rife2*-agent.jar") -} +} \ No newline at end of file From c9f286132c8dd78bac12ad55fd155a7092f261ea Mon Sep 17 00:00:00 2001 From: Cedric Champeau Date: Sun, 5 Mar 2023 13:38:42 +0100 Subject: [PATCH 2/2] Fix reloading of templates This commit fixes how templates were reloaded. There was a bug in the plugin which used the output of the precompiled templates for development only dependencies, instead of the templates directory. This caused the templates to be always compiled and added as a resource on runtime classpath, when we only wanted the raw templates. This commit also adds functional tests to the build logic, which can be executed by running `./gradlew build-logic:test`. --- app/build.gradle.kts | 10 +- build-logic/build.gradle.kts | 11 ++ build-logic/settings.gradle.kts | 8 + .../com/uwyn/rife2/gradle/Rife2Plugin.java | 11 +- .../src/test-projects/minimal/build.gradle | 40 ++++ .../src/test-projects/minimal/settings.gradle | 1 + .../minimal/src/main/java/hello/App.java | 16 ++ .../minimal/src/main/java/hello/AppUber.java | 11 ++ .../META-INF/native-image/reflect-config.json | 6 + .../native-image/resource-config.json | 8 + .../minimal/src/main/templates/hello.html | 11 ++ .../minimal/src/main/webapp/css/style.css | 21 ++ .../minimal/src/test/java/hello/AppTest.java | 24 +++ .../gradle/AbstractFunctionalTest.groovy | 184 ++++++++++++++++++ .../uwyn/rife2/gradle/PackagingTest.groovy | 35 ++++ .../com/uwyn/rife2/gradle/TeeWriter.groovy | 81 ++++++++ .../gradle/TemplateCompilationTest.groovy | 58 ++++++ gradle/libs.versions.toml | 17 ++ 18 files changed, 542 insertions(+), 11 deletions(-) create mode 100644 build-logic/src/test-projects/minimal/build.gradle create mode 100644 build-logic/src/test-projects/minimal/settings.gradle create mode 100644 build-logic/src/test-projects/minimal/src/main/java/hello/App.java create mode 100644 build-logic/src/test-projects/minimal/src/main/java/hello/AppUber.java create mode 100644 build-logic/src/test-projects/minimal/src/main/resources/META-INF/native-image/reflect-config.json create mode 100644 build-logic/src/test-projects/minimal/src/main/resources/META-INF/native-image/resource-config.json create mode 100644 build-logic/src/test-projects/minimal/src/main/templates/hello.html create mode 100644 build-logic/src/test-projects/minimal/src/main/webapp/css/style.css create mode 100644 build-logic/src/test-projects/minimal/src/test/java/hello/AppTest.java create mode 100644 build-logic/src/test/groovy/com/uwyn/rife2/gradle/AbstractFunctionalTest.groovy create mode 100644 build-logic/src/test/groovy/com/uwyn/rife2/gradle/PackagingTest.groovy create mode 100644 build-logic/src/test/groovy/com/uwyn/rife2/gradle/TeeWriter.groovy create mode 100644 build-logic/src/test/groovy/com/uwyn/rife2/gradle/TemplateCompilationTest.groovy create mode 100644 gradle/libs.versions.toml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 07daedc..29c1b77 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -33,16 +33,14 @@ repositories { rife2 { version.set("1.4.0") useAgent.set(true) - precompiledTemplateTypes.addAll(HTML) } dependencies { - runtimeOnly("org.eclipse.jetty:jetty-server:11.0.13") - runtimeOnly("org.eclipse.jetty:jetty-servlet:11.0.13") - runtimeOnly("org.slf4j:slf4j-simple:2.0.5") + runtimeOnly(libs.bundles.jetty) + runtimeOnly(libs.slf4j.simple) - testImplementation("org.jsoup:jsoup:1.15.3") - testImplementation("org.junit.jupiter:junit-jupiter:5.9.1") + testImplementation(libs.jsoup) + testImplementation(libs.junit.jupiter) } tasks { diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index b29e98f..c7fabff 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -1,5 +1,6 @@ plugins { `java-gradle-plugin` + groovy } repositories { @@ -8,6 +9,8 @@ repositories { dependencies { gradleApi() + testImplementation(libs.spock.core) + testImplementation(gradleTestKit()) } gradlePlugin { @@ -18,3 +21,11 @@ gradlePlugin { } } } + +tasks.withType().configureEach { + useJUnitPlatform() + testLogging { + exceptionFormat = org.gradle.api.tasks.testing.logging.TestExceptionFormat.FULL + events = setOf(org.gradle.api.tasks.testing.logging.TestLogEvent.PASSED, org.gradle.api.tasks.testing.logging.TestLogEvent.SKIPPED, org.gradle.api.tasks.testing.logging.TestLogEvent.FAILED) + } +} diff --git a/build-logic/settings.gradle.kts b/build-logic/settings.gradle.kts index 7fbbd44..9ac59bd 100644 --- a/build-logic/settings.gradle.kts +++ b/build-logic/settings.gradle.kts @@ -1 +1,9 @@ rootProject.name = "build-logic" + +dependencyResolutionManagement { + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} diff --git a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java index 9a1b5fe..2777742 100644 --- a/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java +++ b/build-logic/src/main/java/com/uwyn/rife2/gradle/Rife2Plugin.java @@ -45,6 +45,7 @@ public class Rife2Plugin implements Plugin { public static final String DEFAULT_GENERATED_RIFE2_CLASSES_DIR = "generated/classes/rife2"; public static final String RIFE2_GROUP = "rife2"; public static final String WEBAPP_SRCDIR = "src/main/webapp"; + public static final String PRECOMPILE_TEMPLATES_TASK_NAME = "precompileTemplates"; @Override public void apply(Project project) { @@ -62,7 +63,7 @@ public class Rife2Plugin implements Plugin { configurations.getByName(JavaPlugin.IMPLEMENTATION_CONFIGURATION_NAME).extendsFrom(rife2Configuration); var precompileTemplates = registerPrecompileTemplateTask(project, rife2CompilerClasspath, rife2Extension); - createRife2DevelopmentOnlyConfiguration(project, configurations, dependencyHandler, precompileTemplates); + createRife2DevelopmentOnlyConfiguration(project, configurations, dependencyHandler); exposePrecompiledTemplatesToTestTask(project, configurations, dependencyHandler, precompileTemplates); configureAgent(project, plugins, rife2Extension, rife2AgentClasspath); TaskProvider uberJarTask = registerUberJarTask(project, plugins, javaPluginExtension, rife2Extension, tasks, precompileTemplates); @@ -118,14 +119,13 @@ public class Rife2Plugin implements Plugin { private void createRife2DevelopmentOnlyConfiguration(Project project, ConfigurationContainer configurations, - DependencyHandler dependencies, - TaskProvider precompileTemplatesTask) { + DependencyHandler dependencies) { var rife2DevelopmentOnly = configurations.create("rife2DevelopmentOnly", conf -> { conf.setDescription("Dependencies which should only be visible when running the application in development mode (and not in tests)."); conf.setCanBeConsumed(false); conf.setCanBeResolved(false); }); - rife2DevelopmentOnly.getDependencies().add(dependencies.create(project.files(precompileTemplatesTask))); + rife2DevelopmentOnly.getDependencies().add(dependencies.create(project.files(DEFAULT_TEMPLATES_DIR))); configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME).extendsFrom(rife2DevelopmentOnly); } @@ -174,6 +174,7 @@ public class Rife2Plugin implements Plugin { rife2.getUseAgent().convention(false); rife2.getUberMainClass().convention(project.getExtensions().getByType(JavaApplication.class).getMainClass() .map(mainClass -> mainClass + "Uber")); + rife2.getPrecompiledTemplateTypes().convention(Collections.singletonList(TemplateType.HTML)); return rife2; } @@ -216,7 +217,7 @@ public class Rife2Plugin implements Plugin { private static TaskProvider registerPrecompileTemplateTask(Project project, Configuration rife2CompilerClasspath, Rife2Extension rife2Extension) { - return project.getTasks().register("precompileTemplates", PrecompileTemplates.class, task -> { + return project.getTasks().register(PRECOMPILE_TEMPLATES_TASK_NAME, PrecompileTemplates.class, task -> { task.setGroup(RIFE2_GROUP); task.setDescription("Pre-compiles the templates."); task.getVerbose().convention(true); diff --git a/build-logic/src/test-projects/minimal/build.gradle b/build-logic/src/test-projects/minimal/build.gradle new file mode 100644 index 0000000..697f9f5 --- /dev/null +++ b/build-logic/src/test-projects/minimal/build.gradle @@ -0,0 +1,40 @@ +import com.uwyn.rife2.gradle.TemplateType.* + +plugins { + id("application") + id("com.uwyn.rife2") +} + +base { + archivesName = "hello" + version = 1.0 + group = "com.example" +} + +application { + mainClass = "hello.App" +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +repositories { + mavenCentral() +} + +rife2 { + version = "1.4.0" + useAgent = true +} + +dependencies { + runtimeOnly("org.eclipse.jetty:jetty-server:11.0.13") + runtimeOnly("org.eclipse.jetty:jetty-servlet:11.0.13") + runtimeOnly("org.slf4j:slf4j-simple:2.0.5") + + testImplementation("org.jsoup:jsoup:1.15.3") + testImplementation("org.junit.jupiter:junit-jupiter:5.9.1") +} diff --git a/build-logic/src/test-projects/minimal/settings.gradle b/build-logic/src/test-projects/minimal/settings.gradle new file mode 100644 index 0000000..e87ab3c --- /dev/null +++ b/build-logic/src/test-projects/minimal/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'minimal' diff --git a/build-logic/src/test-projects/minimal/src/main/java/hello/App.java b/build-logic/src/test-projects/minimal/src/main/java/hello/App.java new file mode 100644 index 0000000..43a1870 --- /dev/null +++ b/build-logic/src/test-projects/minimal/src/main/java/hello/App.java @@ -0,0 +1,16 @@ +package hello; + +import rife.engine.*; + +public class App extends Site { + public void setup() { + var hello = get("/hello", c -> c.print(c.template("hello"))); + get("/", c -> c.redirect(hello)); + } + + public static void main(String[] args) { + new Server() + .staticResourceBase("src/main/webapp") + .start(new App()); + } +} diff --git a/build-logic/src/test-projects/minimal/src/main/java/hello/AppUber.java b/build-logic/src/test-projects/minimal/src/main/java/hello/AppUber.java new file mode 100644 index 0000000..8dbf6a6 --- /dev/null +++ b/build-logic/src/test-projects/minimal/src/main/java/hello/AppUber.java @@ -0,0 +1,11 @@ +package hello; + +import rife.engine.Server; + +public class AppUber extends App { + public static void main(String[] args) { + new Server() + .staticUberJarResourceBase("webapp") + .start(new AppUber()); + } +} \ No newline at end of file diff --git a/build-logic/src/test-projects/minimal/src/main/resources/META-INF/native-image/reflect-config.json b/build-logic/src/test-projects/minimal/src/main/resources/META-INF/native-image/reflect-config.json new file mode 100644 index 0000000..9b0c3be --- /dev/null +++ b/build-logic/src/test-projects/minimal/src/main/resources/META-INF/native-image/reflect-config.json @@ -0,0 +1,6 @@ +[ +{ + "name":"rife.template.html.hello", + "methods":[{"name":"","parameterTypes":[] }] +} +] diff --git a/build-logic/src/test-projects/minimal/src/main/resources/META-INF/native-image/resource-config.json b/build-logic/src/test-projects/minimal/src/main/resources/META-INF/native-image/resource-config.json new file mode 100644 index 0000000..ad0b0a3 --- /dev/null +++ b/build-logic/src/test-projects/minimal/src/main/resources/META-INF/native-image/resource-config.json @@ -0,0 +1,8 @@ +{ + "resources":{ + "includes":[ + {"pattern":"^webapp/.*$"} + ] + }, + "bundles":[] +} diff --git a/build-logic/src/test-projects/minimal/src/main/templates/hello.html b/build-logic/src/test-projects/minimal/src/main/templates/hello.html new file mode 100644 index 0000000..59ff81b --- /dev/null +++ b/build-logic/src/test-projects/minimal/src/main/templates/hello.html @@ -0,0 +1,11 @@ + + + + + <!--v title-->Hello<!--/v--> + + + +

Hello World

+ + diff --git a/build-logic/src/test-projects/minimal/src/main/webapp/css/style.css b/build-logic/src/test-projects/minimal/src/main/webapp/css/style.css new file mode 100644 index 0000000..52bf6c7 --- /dev/null +++ b/build-logic/src/test-projects/minimal/src/main/webapp/css/style.css @@ -0,0 +1,21 @@ +:root { + /* fonts */ + --main-font: sans-serif; + + /* font sizes */ + --main-font-size: 18px; + + /* colors */ + --main-background-color: #0d0d0d; + --main-text-color: #d0d0d0; + + /* margins and padding */ + --content-padding: 2em; +} +body { + background: var(--main-background-color); + font-family: var(--main-font); + font-style: var(--main-font-size); + color: var(--main-text-color); + padding: var(--content-padding); +} \ No newline at end of file diff --git a/build-logic/src/test-projects/minimal/src/test/java/hello/AppTest.java b/build-logic/src/test-projects/minimal/src/test/java/hello/AppTest.java new file mode 100644 index 0000000..45a0763 --- /dev/null +++ b/build-logic/src/test-projects/minimal/src/test/java/hello/AppTest.java @@ -0,0 +1,24 @@ +/* + * This Java source file was generated by the Gradle 'init' task. + */ +package hello; + +import org.junit.jupiter.api.Test; +import rife.test.MockConversation; + +import static org.junit.jupiter.api.Assertions.*; + +public class AppTest { + @Test + void verifyRoot() { + var m = new MockConversation(new App()); + assertEquals(m.doRequest("/").getStatus(), 302); + } + + @Test + void verifyHello() { + var m = new MockConversation(new App()); + assertEquals("Hello", m.doRequest("/hello") + .getTemplate().getValue("title")); + } +} diff --git a/build-logic/src/test/groovy/com/uwyn/rife2/gradle/AbstractFunctionalTest.groovy b/build-logic/src/test/groovy/com/uwyn/rife2/gradle/AbstractFunctionalTest.groovy new file mode 100644 index 0000000..7629d3f --- /dev/null +++ b/build-logic/src/test/groovy/com/uwyn/rife2/gradle/AbstractFunctionalTest.groovy @@ -0,0 +1,184 @@ +package com.uwyn.rife2.gradle + +import org.gradle.testkit.runner.BuildResult +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.gradle.util.GFileUtils +import org.gradle.util.GradleVersion +import spock.lang.Specification +import spock.lang.TempDir + +import java.nio.file.Path + +abstract class AbstractFunctionalTest extends Specification { + + private final String gradleVersion = System.getProperty("gradleVersion", GradleVersion.current().version) + + @TempDir + Path testDirectory + + boolean debug + + private StringWriter outputWriter + private StringWriter errorOutputWriter + private String output + private String errorOutput + + BuildResult result + + Path path(String... pathElements) { + Path cur = testDirectory + pathElements.each { + cur = cur.resolve(it) + } + cur + } + + File file(String... pathElements) { + path(pathElements).toFile() + } + + File getGroovyBuildFile() { + file("build.gradle") + } + + File getBuildFile() { + groovyBuildFile + } + + File getKotlinBuildFile() { + file("build.gradle.kts") + } + + File getGroovySettingsFile() { + file("settings.gradle") + } + + File getKotlinSettingsFile() { + file("settings.gradle.kts") + } + + File getSettingsFile() { + groovySettingsFile + } + + void run(String... args) { + try { + result = newRunner(args) + .build() + } finally { + recordOutputs() + } + } + + void outputContains(String text) { + assert output.normalize().contains(text.normalize()) + } + + void outputDoesNotContain(String text) { + assert !output.normalize().contains(text.normalize()) + } + + void errorOutputContains(String text) { + assert errorOutput.normalize().contains(text.normalize()) + } + + void tasks(@DelegatesTo(value = TaskExecutionGraph, strategy = Closure.DELEGATE_FIRST) Closure spec) { + def graph = new TaskExecutionGraph() + spec.delegate = graph + spec.resolveStrategy = Closure.DELEGATE_FIRST + spec() + } + + private void recordOutputs() { + output = outputWriter.toString() + errorOutput = errorOutputWriter.toString() + } + + private GradleRunner newRunner(String... args) { + outputWriter = new StringWriter() + errorOutputWriter = new StringWriter() + ArrayList autoArgs = computeAutoArgs() + def runner = GradleRunner.create() + .forwardStdOutput(tee(new OutputStreamWriter(System.out), outputWriter)) + .forwardStdError(tee(new OutputStreamWriter(System.err), errorOutputWriter)) + .withPluginClasspath() + .withProjectDir(testDirectory.toFile()) + .withArguments([*autoArgs, *args]) + if (gradleVersion) { + runner.withGradleVersion(gradleVersion) + } + if (debug) { + runner.withDebug(true) + } + runner + } + + private ArrayList computeAutoArgs() { + List autoArgs = [ + "-s", + "--console=verbose" + ] + if (Boolean.getBoolean("config.cache")) { + autoArgs << '--configuration-cache' + } + autoArgs + } + + private static Writer tee(Writer one, Writer two) { + return TeeWriter.of(one, two) + } + + void fails(String... args) { + try { + result = newRunner(args) + .buildAndFail() + } finally { + recordOutputs() + } + } + + private class TaskExecutionGraph { + void succeeded(String... tasks) { + tasks.each { task -> + contains(task) + assert result.task(task).outcome == TaskOutcome.SUCCESS + } + } + + void failed(String... tasks) { + tasks.each { task -> + contains(task) + assert result.task(task).outcome == TaskOutcome.FAILED + } + } + + void skipped(String... tasks) { + tasks.each { task -> + contains(task) + assert result.task(task).outcome == TaskOutcome.SKIPPED + } + } + + void contains(String... tasks) { + tasks.each { task -> + assert result.task(task) != null: "Expected to find task $task in the graph but it was missing" + } + } + + void doesNotContain(String... tasks) { + tasks.each { task -> + assert result.task(task) == null: "Task $task should be missing from the task graph but it was found with an outcome of ${result.task(task).outcome}" + } + } + } + + void usesProject(String name) { + File sampleDir = new File("src/test-projects/$name") + GFileUtils.copyDirectory(sampleDir, testDirectory.toFile()) + } + + File file(String path) { + new File(testDirectory.toFile(), path) + } +} diff --git a/build-logic/src/test/groovy/com/uwyn/rife2/gradle/PackagingTest.groovy b/build-logic/src/test/groovy/com/uwyn/rife2/gradle/PackagingTest.groovy new file mode 100644 index 0000000..e08ff3c --- /dev/null +++ b/build-logic/src/test/groovy/com/uwyn/rife2/gradle/PackagingTest.groovy @@ -0,0 +1,35 @@ +package com.uwyn.rife2.gradle + +import java.nio.file.FileSystems +import java.nio.file.Files + +class PackagingTest extends AbstractFunctionalTest { + def setup() { + usesProject("minimal") + } + + def "#archive contains compiled resources"() { + def jarFile = file(archive).toPath() + when: + run task + + then: "compiles templates are found in the archive" + tasks { + succeeded ":${Rife2Plugin.PRECOMPILE_TEMPLATES_TASK_NAME}" + } + Files.exists(jarFile) + try (def fs = FileSystems.newFileSystem(jarFile, [:])) { + fs.getRootDirectories().each { + Files.walk(it).forEach { path -> + println path + } + } + assert Files.exists(fs.getPath("/rife/template/html/hello.class")) + } + + where: + task | archive + 'jar' | 'build/libs/hello-1.0.jar' + 'uberJar' | 'build/libs/hello-uber-1.0.jar' + } +} diff --git a/build-logic/src/test/groovy/com/uwyn/rife2/gradle/TeeWriter.groovy b/build-logic/src/test/groovy/com/uwyn/rife2/gradle/TeeWriter.groovy new file mode 100644 index 0000000..89f60c1 --- /dev/null +++ b/build-logic/src/test/groovy/com/uwyn/rife2/gradle/TeeWriter.groovy @@ -0,0 +1,81 @@ +package com.uwyn.rife2.gradle + +import groovy.transform.CompileStatic + +@CompileStatic +class TeeWriter extends Writer { + private final Writer one + private final Writer two + + static TeeWriter of(Writer one, Writer two) { + new TeeWriter(one, two) + } + + private TeeWriter(Writer one, Writer two) { + this.one = one + this.two = two + } + + @Override + void write(int c) throws IOException { + try { + one.write(c) + } finally { + two.write(c) + } + } + + @Override + void write(char[] cbuf) throws IOException { + try { + one.write(cbuf) + } finally { + two.write(cbuf) + } + } + + @Override + void write(char[] cbuf, int off, int len) throws IOException { + try { + one.write(cbuf, off, len) + } finally { + two.write(cbuf, off, len) + } + } + + @Override + void write(String str) throws IOException { + try { + one.write(str) + } finally { + two.write(str) + } + } + + @Override + void write(String str, int off, int len) throws IOException { + try { + one.write(str, off, len) + } finally { + two.write(str, off, len) + } + } + + @Override + void flush() throws IOException { + try { + one.flush() + } finally { + two.flush() + } + } + + @Override + void close() throws IOException { + try { + one.close() + } finally { + two.close() + } + } +} diff --git a/build-logic/src/test/groovy/com/uwyn/rife2/gradle/TemplateCompilationTest.groovy b/build-logic/src/test/groovy/com/uwyn/rife2/gradle/TemplateCompilationTest.groovy new file mode 100644 index 0000000..12f14a6 --- /dev/null +++ b/build-logic/src/test/groovy/com/uwyn/rife2/gradle/TemplateCompilationTest.groovy @@ -0,0 +1,58 @@ +package com.uwyn.rife2.gradle + +class TemplateCompilationTest extends AbstractFunctionalTest { + def setup() { + usesProject("minimal") + } + + def "doesn't precompile templates when calling `run`"() { + given: + buildFile << """ + tasks.named("run") { + doFirst { + throw new RuntimeException("force stop") + } + } + """ + when: + fails 'run' + + then: "precompile templates task must not be present in task graph" + errorOutputContains("force stop") + tasks { + doesNotContain ":${Rife2Plugin.PRECOMPILE_TEMPLATES_TASK_NAME}" + } + } + + def "`run` task classpath includes template sources"() { + given: + buildFile << """ + tasks.register("dumpRunClasspath") { + doLast { + tasks.named("run").get().classpath.files.each { + println "Classpath entry: \$it" + } + } + } + """ + + when: + run("dumpRunClasspath") + + then: "template sources must be present in the classpath" + outputContains("Classpath entry: ${file("src/main/templates").absolutePath}") + } + + def "compiles templates when running #task"() { + when: + run task + + then: "precompile templates task must be present in task graph" + tasks { + succeeded ":${Rife2Plugin.PRECOMPILE_TEMPLATES_TASK_NAME}" + } + + where: + task << ['jar', 'test', 'uberJar'] + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..3a8c200 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,17 @@ +[versions] +jetty = "11.0.13" +jsoup = "1.15.3" +junit-jupiter = "5.9.1" +slf4j = "2.0.5" +spock = "2.3-groovy-3.0" + +[libraries] +jetty-server = { module = "org.eclipse.jetty:jetty-server", version.ref = "jetty" } +jetty-servlet = { module = "org.eclipse.jetty:jetty-servlet", version.ref = "jetty" } +jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } +junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" } +slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" } +spock-core = { module = "org.spockframework:spock-core", version.ref = "spock" } + +[bundles] +jetty = [ "jetty-server", "jetty-servlet" ]