diff --git a/.github/workflows/bld.yml b/.github/workflows/bld.yml
index 944b4a9..88fdacb 100644
--- a/.github/workflows/bld.yml
+++ b/.github/workflows/bld.yml
@@ -7,7 +7,6 @@ jobs:
strategy:
matrix:
java-version: [ 17, 21, 24 ]
- kotlin-version: [ 1.9.25, 2.0.21, 2.1.20 ]
os: [ ubuntu-latest, windows-latest, macos-latest ]
runs-on: ${{ matrix.os }}
@@ -44,4 +43,4 @@ jobs:
run: ./bld download
- name: Run tests
- run: ./bld compile test
\ No newline at end of file
+ run: ./bld compile test
diff --git a/.idea/misc.xml b/.idea/misc.xml
index dc8f8c0..6fc2b64 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -18,7 +18,7 @@
-
+
\ No newline at end of file
diff --git a/.idea/runConfigurations/Run Tests.xml b/.idea/runConfigurations/BootOperationTest.xml
similarity index 62%
rename from .idea/runConfigurations/Run Tests.xml
rename to .idea/runConfigurations/BootOperationTest.xml
index 761a607..6d98a3f 100644
--- a/.idea/runConfigurations/Run Tests.xml
+++ b/.idea/runConfigurations/BootOperationTest.xml
@@ -1,6 +1,6 @@
-
-
+
+
diff --git a/examples/src/bld/java/com/example/demo/DemoApplicationBuild.java b/examples/src/bld/java/com/example/demo/DemoApplicationBuild.java
index e9c9276..ddfb33f 100644
--- a/examples/src/bld/java/com/example/demo/DemoApplicationBuild.java
+++ b/examples/src/bld/java/com/example/demo/DemoApplicationBuild.java
@@ -31,7 +31,7 @@ public class DemoApplicationBuild extends WebProject {
.include(dependency("org.springframework.boot", "spring-boot-starter", boot))
.include(dependency("org.springframework.boot", "spring-boot-starter-actuator", boot))
.include(dependency("org.springframework.boot", "spring-boot-starter-web", boot))
- .include(dependency("org.mockito:mockito-core:5.17.0"));
+ .include(dependency("org.mockito:mockito-core:5.18.0"));
scope(test)
.include(dependency("org.springframework.boot", "spring-boot-starter-test", boot))
.include(dependency("org.junit.jupiter", "junit-jupiter", version(5, 12, 2)))
diff --git a/lib/bld/bld-wrapper.properties b/lib/bld/bld-wrapper.properties
index 8d8e3e4..43cf409 100644
--- a/lib/bld/bld-wrapper.properties
+++ b/lib/bld/bld-wrapper.properties
@@ -1,7 +1,8 @@
bld.downloadExtensionJavadoc=false
bld.downloadExtensionSources=true
bld.downloadLocation=
-bld.extension-pmd=com.uwyn.rife2:bld-pmd:1.2.2
+bld.extension-exec=com.uwyn.rife2:bld-exec:1.0.5
+bld.extension-pmd=com.uwyn.rife2:bld-pmd:1.2.3
bld.repositories=MAVEN_CENTRAL,MAVEN_LOCAL,RIFE2_RELEASES
bld.sourceDirectories=
bld.version=2.2.1
diff --git a/src/bld/java/rife/bld/extension/SpringBootBuild.java b/src/bld/java/rife/bld/extension/SpringBootBuild.java
index d243d97..ae8705d 100644
--- a/src/bld/java/rife/bld/extension/SpringBootBuild.java
+++ b/src/bld/java/rife/bld/extension/SpringBootBuild.java
@@ -22,6 +22,9 @@ import rife.bld.publish.PublishDeveloper;
import rife.bld.publish.PublishLicense;
import rife.bld.publish.PublishScm;
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.List;
import static rife.bld.dependencies.Repository.*;
@@ -94,4 +97,34 @@ public class SpringBootBuild extends Project {
.ruleSets("config/pmd.xml")
.execute();
}
+
+ @Override
+ public void test() throws Exception {
+ var testResultsDir = "build/test-results/test/";
+ var op = testOperation().fromProject(this);
+ op.testToolOptions().reportsDir(new File(testResultsDir));
+
+ Exception ex = null;
+ try {
+ op.execute();
+ } catch (Exception e) {
+ ex = e;
+ }
+
+ var xunitViewer = new File("/usr/bin/xunit-viewer");
+ if (xunitViewer.exists() && xunitViewer.canExecute()) {
+ var reportsDir = "build/reports/tests/test/";
+
+ Files.createDirectories(Path.of(reportsDir));
+
+ new ExecOperation()
+ .fromProject(this)
+ .command(xunitViewer.getPath(), "-r", testResultsDir, "-o", reportsDir + "index.html")
+ .execute();
+ }
+
+ if (ex != null) {
+ throw ex;
+ }
+ }
}
diff --git a/src/main/java/rife/bld/extension/BootUtils.java b/src/main/java/rife/bld/extension/BootUtils.java
index 42ae132..0318918 100644
--- a/src/main/java/rife/bld/extension/BootUtils.java
+++ b/src/main/java/rife/bld/extension/BootUtils.java
@@ -37,7 +37,7 @@ public final class BootUtils {
}
/**
- * Calculates the given file size in bytes, kilobytes, megabytes, gigabytes or terabytes.
+ * Calculates the given file size in bytes, kilobytes, megabytes, gigabytes, or terabytes.
*
* @param file the file
* @return the file size in B, KB, MB, GB, or TB.
diff --git a/src/test/java/rife/bld/extension/BootJarOperationTest.java b/src/test/java/rife/bld/extension/BootJarOperationTest.java
deleted file mode 100644
index 5ee7341..0000000
--- a/src/test/java/rife/bld/extension/BootJarOperationTest.java
+++ /dev/null
@@ -1,471 +0,0 @@
-/*
- * Copyright 2023-2025 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 rife.bld.extension;
-
-import org.assertj.core.api.AutoCloseableSoftAssertions;
-import org.junit.jupiter.api.Test;
-import rife.bld.Project;
-import rife.bld.dependencies.VersionNumber;
-import rife.tools.FileUtils;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Enumeration;
-import java.util.List;
-import java.util.jar.JarEntry;
-import java.util.jar.JarFile;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatCode;
-
-class BootJarOperationTest {
- private static final String BLD = "bld-2.2.1.jar";
- private static final String BOOT_VERSION = "3.4.5";
- private static final String EXAMPLES_LIB_COMPILE = "examples/lib/compile/";
- private static final String EXAMPLES_LIB_RUNTIME = "examples/lib/runtime/";
- private static final String EXAMPLES_LIB_STANDALONE = "examples/lib/standalone/";
- private static final String LAUNCHER_JARS = """
- org/
- org/springframework/
- org/springframework/boot/
- org/springframework/boot/loader/
- org/springframework/boot/loader/jar/
- org/springframework/boot/loader/jar/JarEntriesStream$InputStreamSupplier.class
- org/springframework/boot/loader/jar/JarEntriesStream.class
- org/springframework/boot/loader/jar/ManifestInfo.class
- org/springframework/boot/loader/jar/MetaInfVersionsInfo.class
- org/springframework/boot/loader/jar/NestedJarFile$JarEntriesEnumeration.class
- org/springframework/boot/loader/jar/NestedJarFile$JarEntryInflaterInputStream.class
- org/springframework/boot/loader/jar/NestedJarFile$JarEntryInputStream.class
- org/springframework/boot/loader/jar/NestedJarFile$NestedJarEntry.class
- org/springframework/boot/loader/jar/NestedJarFile$RawZipDataInputStream.class
- org/springframework/boot/loader/jar/NestedJarFile$ZipContentEntriesSpliterator.class
- org/springframework/boot/loader/jar/NestedJarFile.class
- org/springframework/boot/loader/jar/NestedJarFileResources.class
- org/springframework/boot/loader/jar/SecurityInfo.class
- org/springframework/boot/loader/jar/ZipInflaterInputStream.class
- org/springframework/boot/loader/jarmode/
- org/springframework/boot/loader/jarmode/JarMode.class
- org/springframework/boot/loader/jarmode/JarModeErrorException.class
- org/springframework/boot/loader/launch/
- org/springframework/boot/loader/launch/Archive$Entry.class
- org/springframework/boot/loader/launch/Archive.class
- org/springframework/boot/loader/launch/ClassPathIndexFile.class
- org/springframework/boot/loader/launch/ExecutableArchiveLauncher.class
- org/springframework/boot/loader/launch/ExplodedArchive$FileArchiveEntry.class
- org/springframework/boot/loader/launch/ExplodedArchive.class
- org/springframework/boot/loader/launch/JarFileArchive$JarArchiveEntry.class
- org/springframework/boot/loader/launch/JarFileArchive.class
- org/springframework/boot/loader/launch/JarLauncher.class
- org/springframework/boot/loader/launch/JarModeRunner.class
- org/springframework/boot/loader/launch/LaunchedClassLoader$DefinePackageCallType.class
- org/springframework/boot/loader/launch/LaunchedClassLoader.class
- org/springframework/boot/loader/launch/Launcher.class
- org/springframework/boot/loader/launch/PropertiesLauncher$Instantiator$Using.class
- org/springframework/boot/loader/launch/PropertiesLauncher$Instantiator.class
- org/springframework/boot/loader/launch/PropertiesLauncher.class
- org/springframework/boot/loader/launch/SystemPropertyUtils.class
- org/springframework/boot/loader/launch/WarLauncher.class
- org/springframework/boot/loader/log/
- org/springframework/boot/loader/log/DebugLogger$DisabledDebugLogger.class
- org/springframework/boot/loader/log/DebugLogger$SystemErrDebugLogger.class
- org/springframework/boot/loader/log/DebugLogger.class
- org/springframework/boot/loader/net/
- org/springframework/boot/loader/net/protocol/
- org/springframework/boot/loader/net/protocol/Handlers.class
- org/springframework/boot/loader/net/protocol/jar/
- org/springframework/boot/loader/net/protocol/jar/Canonicalizer.class
- org/springframework/boot/loader/net/protocol/jar/Handler.class
- org/springframework/boot/loader/net/protocol/jar/JarFileUrlKey.class
- org/springframework/boot/loader/net/protocol/jar/JarUrl.class
- org/springframework/boot/loader/net/protocol/jar/JarUrlClassLoader$OptimizedEnumeration.class
- org/springframework/boot/loader/net/protocol/jar/JarUrlClassLoader.class
- org/springframework/boot/loader/net/protocol/jar/JarUrlConnection$ConnectionInputStream.class
- org/springframework/boot/loader/net/protocol/jar/JarUrlConnection$EmptyUrlStreamHandler.class
- org/springframework/boot/loader/net/protocol/jar/JarUrlConnection.class
- org/springframework/boot/loader/net/protocol/jar/LazyDelegatingInputStream.class
- org/springframework/boot/loader/net/protocol/jar/Optimizations.class
- org/springframework/boot/loader/net/protocol/jar/UrlJarEntry.class
- org/springframework/boot/loader/net/protocol/jar/UrlJarFile.class
- org/springframework/boot/loader/net/protocol/jar/UrlJarFileFactory.class
- org/springframework/boot/loader/net/protocol/jar/UrlJarFiles$Cache.class
- org/springframework/boot/loader/net/protocol/jar/UrlJarFiles.class
- org/springframework/boot/loader/net/protocol/jar/UrlJarManifest$ManifestSupplier.class
- org/springframework/boot/loader/net/protocol/jar/UrlJarManifest.class
- org/springframework/boot/loader/net/protocol/jar/UrlNestedJarFile.class
- org/springframework/boot/loader/net/protocol/nested/
- org/springframework/boot/loader/net/protocol/nested/Handler.class
- org/springframework/boot/loader/net/protocol/nested/NestedLocation.class
- org/springframework/boot/loader/net/protocol/nested/NestedUrlConnection$ConnectionInputStream.class
- org/springframework/boot/loader/net/protocol/nested/NestedUrlConnection.class
- org/springframework/boot/loader/net/protocol/nested/NestedUrlConnectionResources.class
- org/springframework/boot/loader/net/util/
- org/springframework/boot/loader/net/util/UrlDecoder.class
- org/springframework/boot/loader/nio/
- org/springframework/boot/loader/nio/file/
- org/springframework/boot/loader/nio/file/NestedByteChannel$Resources.class
- org/springframework/boot/loader/nio/file/NestedByteChannel.class
- org/springframework/boot/loader/nio/file/NestedFileStore.class
- org/springframework/boot/loader/nio/file/NestedFileSystem.class
- org/springframework/boot/loader/nio/file/NestedFileSystemProvider.class
- org/springframework/boot/loader/nio/file/NestedPath.class
- org/springframework/boot/loader/nio/file/UriPathEncoder.class
- org/springframework/boot/loader/ref/
- org/springframework/boot/loader/ref/Cleaner.class
- org/springframework/boot/loader/ref/DefaultCleaner.class
- org/springframework/boot/loader/zip/
- org/springframework/boot/loader/zip/ByteArrayDataBlock.class
- org/springframework/boot/loader/zip/CloseableDataBlock.class
- org/springframework/boot/loader/zip/DataBlock.class
- org/springframework/boot/loader/zip/DataBlockInputStream.class
- org/springframework/boot/loader/zip/FileDataBlock$FileAccess.class
- org/springframework/boot/loader/zip/FileDataBlock$Tracker$1.class
- org/springframework/boot/loader/zip/FileDataBlock$Tracker.class
- org/springframework/boot/loader/zip/FileDataBlock.class
- org/springframework/boot/loader/zip/NameOffsetLookups.class
- org/springframework/boot/loader/zip/VirtualDataBlock.class
- org/springframework/boot/loader/zip/VirtualZipDataBlock$DataPart.class
- org/springframework/boot/loader/zip/VirtualZipDataBlock.class
- org/springframework/boot/loader/zip/Zip64EndOfCentralDirectoryLocator.class
- org/springframework/boot/loader/zip/Zip64EndOfCentralDirectoryRecord.class
- org/springframework/boot/loader/zip/ZipCentralDirectoryFileHeaderRecord.class
- org/springframework/boot/loader/zip/ZipContent$Entry.class
- org/springframework/boot/loader/zip/ZipContent$Kind.class
- org/springframework/boot/loader/zip/ZipContent$Loader.class
- org/springframework/boot/loader/zip/ZipContent$Source.class
- org/springframework/boot/loader/zip/ZipContent.class
- org/springframework/boot/loader/zip/ZipDataDescriptorRecord.class
- org/springframework/boot/loader/zip/ZipEndOfCentralDirectoryRecord$Located.class
- org/springframework/boot/loader/zip/ZipEndOfCentralDirectoryRecord.class
- org/springframework/boot/loader/zip/ZipLocalFileHeaderRecord.class
- org/springframework/boot/loader/zip/ZipString$CompareType.class
- org/springframework/boot/loader/zip/ZipString.class
- """;
- private static final String MAIN_CLASS = "com.example.Foo";
- private static final String PROVIDED_LIB = "LatencyUtils-2.0.3.jar";
- private static final String SPRING_BOOT = "spring-boot-" + BOOT_VERSION + ".jar";
- private static final String SPRING_BOOT_ACTUATOR = "spring-boot-actuator-" + BOOT_VERSION + ".jar";
- private static final String SPRING_BOOT_LOADER = "spring-boot-loader-" + BOOT_VERSION + ".jar";
- private static final String SRC_MAIN_JAVA = "src/main/java";
- private static final String SRC_TEST_JAVA = "src/test/java";
-
- private StringBuilder readJarEntries(File jar) throws IOException {
- var jarEntries = new StringBuilder();
- try (var jarFile = new JarFile(jar)) {
- for (Enumeration entries = jarFile.entries(); entries.hasMoreElements(); ) {
- jarEntries.append(entries.nextElement().getName()).append('\n');
- }
- }
- return jarEntries;
- }
-
- @Test
- void testErrors() throws IOException {
- var bootWar = new BootWarOperation();
- assertThatCode(bootWar::execute).isInstanceOf(IllegalArgumentException.class)
- .hasMessageContaining("mainClass");
-
- bootWar = bootWar.mainClass(MAIN_CLASS);
- assertThatCode(bootWar::execute)
- .isInstanceOf(IllegalArgumentException.class)
- .hasMessageContaining("class required");
-
- assertThatCode(() -> new BootWarOperation().launcherLibs(new File("foo")))
- .as("foo")
- .isInstanceOf(IOException.class)
- .hasMessageContaining("not found");
-
- assertThatCode(() -> new BootWarOperation().launcherLibs("bar"))
- .as("bar")
- .isInstanceOf(IOException.class)
- .hasMessageContaining("not found");
-
- bootWar = bootWar.launcherLibs(List.of(new File(EXAMPLES_LIB_STANDALONE + SPRING_BOOT_LOADER)));
- assertThatCode(bootWar::execute)
- .isInstanceOf((IllegalArgumentException.class))
- .hasMessageContaining("class required");
-
- bootWar = bootWar.launcherClass("org.springframework.boot.loader.launch.WarLauncher");
- assertThat(bootWar.verifyExecute()).isTrue();
- }
-
- @Test
- void testInfLibs() {
- var op = new BootWarOperation();
-
- var foo = new File(EXAMPLES_LIB_COMPILE + SPRING_BOOT);
- var bar = new File(EXAMPLES_LIB_COMPILE + SPRING_BOOT_ACTUATOR);
-
- op.infLibs(EXAMPLES_LIB_COMPILE + SPRING_BOOT, EXAMPLES_LIB_COMPILE + SPRING_BOOT_ACTUATOR);
- assertThat(op.infLibs()).as("String...").containsExactly(foo, bar);
- op.infLibs().clear();
-
- op.infLibs(foo, bar);
- assertThat(op.infLibs()).as("File...").containsExactly(foo, bar);
- op.infLibs().clear();
-
- op.infLibs(foo.toPath(), bar.toPath());
- assertThat(op.infLibs()).as("Path...").containsExactly(foo, bar);
- op.infLibs().clear();
-
- op.infLibsStrings(List.of(EXAMPLES_LIB_COMPILE + SPRING_BOOT, EXAMPLES_LIB_COMPILE + SPRING_BOOT_ACTUATOR));
- assertThat(op.infLibs()).as("List(String...)").containsExactly(foo, bar);
- op.infLibs().clear();
-
- op.infLibs(List.of(foo, bar));
- assertThat(op.infLibs()).as("List(File...)").containsExactly(foo, bar);
- op.infLibs().clear();
-
- op.infLibsPaths(List.of(foo.toPath(), bar.toPath()));
- assertThat(op.infLibs()).as("List(Path...)").containsExactly(foo, bar);
- op.infLibs().clear();
- }
-
- @Test
- @SuppressWarnings("PMD.AvoidDuplicateLiterals")
- void testJarExecute() throws Exception {
- var tmp_dir = Files.createTempDirectory("bootjartmp").toFile();
- var jar = "foo-1.1.1.jar";
- new BootJarOperation()
- .launcherClass("org.springframework.boot.loader.launch.JarLauncher")
- .launcherLibs(List.of(new File(EXAMPLES_LIB_STANDALONE + SPRING_BOOT_LOADER)))
- .destinationDirectory(tmp_dir)
- .destinationFileName(jar)
- .infLibs(new File(EXAMPLES_LIB_COMPILE + SPRING_BOOT),
- new File(EXAMPLES_LIB_COMPILE + SPRING_BOOT_ACTUATOR))
- .mainClass(MAIN_CLASS)
- .sourceDirectories(new File("build/main"))
- .execute();
-
- var jarFile = new File(tmp_dir, jar);
- assertThat(jarFile).exists();
-
- var jarEntries = readJarEntries(jarFile);
- assertThat(jarEntries).isEqualToIgnoringNewLines(
- "BOOT-INF/\n" +
- "BOOT-INF/classes/\n" +
- "BOOT-INF/classes/rife/\n" +
- "BOOT-INF/classes/rife/bld/\n" +
- "BOOT-INF/classes/rife/bld/extension/\n" +
- "BOOT-INF/classes/rife/bld/extension/AbstractBootOperation.class\n" +
- "BOOT-INF/classes/rife/bld/extension/BootJarOperation.class\n" +
- "BOOT-INF/classes/rife/bld/extension/BootUtils.class\n" +
- "BOOT-INF/classes/rife/bld/extension/BootWarOperation.class\n" +
- "BOOT-INF/lib/\n" +
- "BOOT-INF/lib/" + SPRING_BOOT + '\n' +
- "BOOT-INF/lib/" + SPRING_BOOT_ACTUATOR + '\n' +
- "META-INF/\n" +
- "META-INF/MANIFEST.MF\n" + LAUNCHER_JARS);
-
- FileUtils.deleteDirectory(tmp_dir);
- }
-
- @Test
- void testJarProjectExecute() throws Exception {
- var tmp_dir = Files.createTempDirectory("bootwartmp").toFile();
- new BootJarOperation()
- .fromProject(new CustomProject(new File(".")))
- .launcherLibs(List.of(new File(EXAMPLES_LIB_STANDALONE + SPRING_BOOT_LOADER)))
- .destinationDirectory(tmp_dir.getAbsolutePath())
- .infLibs(new File(EXAMPLES_LIB_COMPILE + SPRING_BOOT).getAbsolutePath(),
- new File(EXAMPLES_LIB_COMPILE + SPRING_BOOT_ACTUATOR).getAbsolutePath())
- .execute();
-
- var jarFile = new File(tmp_dir, "test_project-0.0.1-boot.jar");
- assertThat(jarFile).exists();
-
- var jarEntries = readJarEntries(jarFile);
- assertThat(jarEntries).isEqualToIgnoringNewLines(
- "BOOT-INF/\n" +
- "BOOT-INF/classes/\n" +
- "BOOT-INF/classes/rife/\n" +
- "BOOT-INF/classes/rife/bld/\n" +
- "BOOT-INF/classes/rife/bld/extension/\n" +
- "BOOT-INF/classes/rife/bld/extension/AbstractBootOperation.class\n" +
- "BOOT-INF/classes/rife/bld/extension/BootJarOperation.class\n" +
- "BOOT-INF/classes/rife/bld/extension/BootUtils.class\n" +
- "BOOT-INF/classes/rife/bld/extension/BootWarOperation.class\n" +
- "BOOT-INF/lib/\n" +
- "BOOT-INF/lib/" + BLD + '\n' +
- "BOOT-INF/lib/" + SPRING_BOOT + '\n' +
- "BOOT-INF/lib/" + SPRING_BOOT_ACTUATOR + '\n' +
- "META-INF/\n" +
- "META-INF/MANIFEST.MF\n" + LAUNCHER_JARS);
-
- FileUtils.deleteDirectory(tmp_dir);
- }
-
- @Test
- void testLauncherLibs() throws IOException {
- var op = new BootJarOperation();
-
- var launcher = new File(EXAMPLES_LIB_STANDALONE + SPRING_BOOT_LOADER);
- op = op.launcherLibs(EXAMPLES_LIB_STANDALONE + SPRING_BOOT_LOADER);
- assertThat(op.launcherLibs()).as("String...").containsExactly(launcher);
- op.launcherLibs().clear();
-
- op = op.launcherLibs(launcher);
- assertThat(op.launcherLibs()).as("File...").containsExactly(launcher);
- op.launcherLibs().clear();
-
- op = op.launcherLibs(launcher.toPath());
- assertThat(op.launcherLibs()).as("Path...").containsExactly(launcher);
- op.launcherLibs().clear();
-
- op = op.launcherLibsStrings(List.of(EXAMPLES_LIB_STANDALONE + SPRING_BOOT_LOADER));
- assertThat(op.launcherLibs()).as("List(String...)").containsExactly(launcher);
- op.launcherLibs().clear();
-
- op = op.launcherLibs(List.of(launcher));
- assertThat(op.launcherLibs()).as("List(File...)").containsExactly(launcher);
- op.launcherLibs().clear();
-
- op = op.launcherLibsPaths(List.of(launcher.toPath()));
- assertThat(op.launcherLibs()).as("List(Path...)").containsExactly(launcher);
- op.launcherLibs().clear();
- }
-
- @Test
- void testProject() throws IOException {
- var tmp_dir = Files.createTempDirectory("bootprjtmp").toFile();
- var project = new CustomProject(tmp_dir);
- var bootJar = new BootJarOperation().fromProject(project).sourceDirectories(SRC_MAIN_JAVA);
-
- try (var softly = new AutoCloseableSoftAssertions()) {
- softly.assertThat(bootJar.mainClass()).as("mainClass").isEqualTo(MAIN_CLASS);
- softly.assertThat(bootJar.sourceDirectories()).as("sourceDirectories.size").hasSize(3)
- .containsExactly(project.buildMainDirectory(), project.srcMainResourcesDirectory(),
- new File(SRC_MAIN_JAVA));
- softly.assertThat(bootJar.manifestAttributes()).as("manifestAttributes.size").hasSize(3);
- softly.assertThat(bootJar.manifestAttributes().get("Manifest-Version")).as("Manifest-Version")
- .isEqualTo("1.0");
- softly.assertThat(bootJar.manifestAttributes().get("Main-Class")).as("Main-Class").endsWith("JarLauncher");
- softly.assertThat(bootJar.manifestAttributes().get("Start-Class")).as("Start-Class").isEqualTo(MAIN_CLASS);
- softly.assertThat(bootJar.manifestAttribute("Manifest-Test", "tsst")
- .manifestAttributes().get("Manifest-Test")).as("Manifest-Test").isEqualTo("tsst");
- softly.assertThat(bootJar.destinationDirectory()).as("destinationDirectory").isDirectory();
- softly.assertThat(bootJar.destinationDirectory()).isEqualTo(project.buildDistDirectory());
- softly.assertThat(bootJar.infLibs()).as("infoLibs").isEmpty();
- softly.assertThat(bootJar.launcherLibs()).as("launcherJars").isEmpty();
- softly.assertThat(bootJar.destinationFileName()).isEqualTo("test_project-0.0.1-boot.jar");
- }
-
- FileUtils.deleteDirectory(tmp_dir);
- }
-
- @Test
- void testSourceDirectories() {
- var op = new BootJarOperation();
-
- var src = new File(SRC_MAIN_JAVA);
- var test = new File(SRC_TEST_JAVA);
- op = op.sourceDirectories(SRC_MAIN_JAVA, SRC_TEST_JAVA);
- assertThat(op.sourceDirectories()).as("String...").containsExactly(src, test);
- op.sourceDirectories().clear();
-
- op = op.sourceDirectories(src, test);
- assertThat(op.sourceDirectories()).as("File...").containsExactly(src, test);
- op.sourceDirectories().clear();
-
- op = op.sourceDirectories(src.toPath(), test.toPath());
- assertThat(op.sourceDirectories()).as("Path...").containsExactly(src, test);
- op.sourceDirectories().clear();
-
- op.sourceDirectoriesStrings(List.of(SRC_MAIN_JAVA, SRC_TEST_JAVA));
- assertThat(op.sourceDirectories()).as("List(String...").containsExactly(src, test);
- op.sourceDirectories().clear();
-
- op.sourceDirectories(List.of(src, test));
- assertThat(op.sourceDirectories()).as("List(File...)").containsExactly(src, test);
- op.sourceDirectories().clear();
-
- op.sourceDirectoriesPaths(List.of(src.toPath(), test.toPath()));
- assertThat(op.sourceDirectories()).as("List(Path...)").containsExactly(src, test);
- op.sourceDirectories().clear();
- }
-
- @Test
- void testWarProjectExecute() throws Exception {
- var tmp_dir = Files.createTempDirectory("bootjartmp").toFile();
- var project = new CustomProject(new File("."));
- new BootWarOperation()
- .fromProject(project)
- .launcherLibs(List.of(new File(EXAMPLES_LIB_STANDALONE + SPRING_BOOT_LOADER)))
- .destinationDirectory(tmp_dir.toPath())
- .infLibs(Path.of(EXAMPLES_LIB_COMPILE + SPRING_BOOT),
- Path.of(EXAMPLES_LIB_COMPILE + SPRING_BOOT_ACTUATOR))
- .providedLibs(new File(EXAMPLES_LIB_RUNTIME + PROVIDED_LIB))
- .execute();
-
- var warFile = new File(tmp_dir, project.name() + '-' + project.version().toString() + "-boot.war");
- assertThat(warFile).exists();
-
- var jarEntries = readJarEntries(warFile);
- assertThat(jarEntries).isEqualToIgnoringNewLines(
- "META-INF/\n" +
- "META-INF/MANIFEST.MF\n" +
- "WEB-INF/\n" +
- "WEB-INF/classes/\n" +
- "WEB-INF/classes/rife/\n" +
- "WEB-INF/classes/rife/bld/\n" +
- "WEB-INF/classes/rife/bld/extension/\n" +
- "WEB-INF/classes/rife/bld/extension/AbstractBootOperation.class\n" +
- "WEB-INF/classes/rife/bld/extension/BootJarOperation.class\n" +
- "WEB-INF/classes/rife/bld/extension/BootUtils.class\n" +
- "WEB-INF/classes/rife/bld/extension/BootWarOperation.class\n" +
- "WEB-INF/lib/\n" +
- "WEB-INF/lib/" + BLD + '\n' +
- "WEB-INF/lib/dist/\n" +
- "WEB-INF/lib/" + SPRING_BOOT + '\n' +
- "WEB-INF/lib/" + SPRING_BOOT_ACTUATOR + '\n' +
- "WEB-INF/lib-provided/\n" +
- "WEB-INF/lib-provided/" + PROVIDED_LIB + '\n' + LAUNCHER_JARS);
-
- FileUtils.deleteDirectory(tmp_dir);
- }
-
- @Test
- void testWarProvidedLibs() {
- var op = new BootWarOperation();
-
- var foo = new File(EXAMPLES_LIB_RUNTIME + PROVIDED_LIB);
- op = op.providedLibs(EXAMPLES_LIB_RUNTIME + PROVIDED_LIB);
- assertThat(op.providedLibs()).containsExactly(foo);
- op.providedLibs().clear();
-
- op = op.providedLibs(foo);
- assertThat(op.providedLibs()).containsExactly(foo);
- op.providedLibs().clear();
-
- op = op.providedLibs(foo.toPath());
- assertThat(op.providedLibs()).containsExactly(foo);
- op.providedLibs().clear();
- }
-
- static class CustomProject extends Project {
- CustomProject(File tmp) {
- super();
- workDirectory = tmp;
- pkg = "com.example";
- name = "test_project";
- version = new VersionNumber(0, 0, 1);
- mainClass = MAIN_CLASS;
- }
- }
-}
diff --git a/src/test/java/rife/bld/extension/BootOperationTest.java b/src/test/java/rife/bld/extension/BootOperationTest.java
new file mode 100644
index 0000000..c27d603
--- /dev/null
+++ b/src/test/java/rife/bld/extension/BootOperationTest.java
@@ -0,0 +1,563 @@
+/*
+ * Copyright 2023-2025 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 rife.bld.extension;
+
+import org.assertj.core.api.AutoCloseableSoftAssertions;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import rife.bld.Project;
+import rife.bld.dependencies.VersionNumber;
+import rife.tools.FileUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+@SuppressWarnings("PMD.AvoidDuplicateLiterals")
+class BootOperationTest {
+ private static final String BLD = "bld-2.2.1.jar";
+ private static final String BOOT_VERSION = "3.4.5";
+ private static final String EXAMPLES_LIB_COMPILE = "examples/lib/compile/";
+ private static final String EXAMPLES_LIB_RUNTIME = "examples/lib/runtime/";
+ private static final String EXAMPLES_LIB_STANDALONE = "examples/lib/standalone/";
+ private static final String LAUNCHER_JARS = """
+ org/
+ org/springframework/
+ org/springframework/boot/
+ org/springframework/boot/loader/
+ org/springframework/boot/loader/jar/
+ org/springframework/boot/loader/jar/JarEntriesStream$InputStreamSupplier.class
+ org/springframework/boot/loader/jar/JarEntriesStream.class
+ org/springframework/boot/loader/jar/ManifestInfo.class
+ org/springframework/boot/loader/jar/MetaInfVersionsInfo.class
+ org/springframework/boot/loader/jar/NestedJarFile$JarEntriesEnumeration.class
+ org/springframework/boot/loader/jar/NestedJarFile$JarEntryInflaterInputStream.class
+ org/springframework/boot/loader/jar/NestedJarFile$JarEntryInputStream.class
+ org/springframework/boot/loader/jar/NestedJarFile$NestedJarEntry.class
+ org/springframework/boot/loader/jar/NestedJarFile$RawZipDataInputStream.class
+ org/springframework/boot/loader/jar/NestedJarFile$ZipContentEntriesSpliterator.class
+ org/springframework/boot/loader/jar/NestedJarFile.class
+ org/springframework/boot/loader/jar/NestedJarFileResources.class
+ org/springframework/boot/loader/jar/SecurityInfo.class
+ org/springframework/boot/loader/jar/ZipInflaterInputStream.class
+ org/springframework/boot/loader/jarmode/
+ org/springframework/boot/loader/jarmode/JarMode.class
+ org/springframework/boot/loader/jarmode/JarModeErrorException.class
+ org/springframework/boot/loader/launch/
+ org/springframework/boot/loader/launch/Archive$Entry.class
+ org/springframework/boot/loader/launch/Archive.class
+ org/springframework/boot/loader/launch/ClassPathIndexFile.class
+ org/springframework/boot/loader/launch/ExecutableArchiveLauncher.class
+ org/springframework/boot/loader/launch/ExplodedArchive$FileArchiveEntry.class
+ org/springframework/boot/loader/launch/ExplodedArchive.class
+ org/springframework/boot/loader/launch/JarFileArchive$JarArchiveEntry.class
+ org/springframework/boot/loader/launch/JarFileArchive.class
+ org/springframework/boot/loader/launch/JarLauncher.class
+ org/springframework/boot/loader/launch/JarModeRunner.class
+ org/springframework/boot/loader/launch/LaunchedClassLoader$DefinePackageCallType.class
+ org/springframework/boot/loader/launch/LaunchedClassLoader.class
+ org/springframework/boot/loader/launch/Launcher.class
+ org/springframework/boot/loader/launch/PropertiesLauncher$Instantiator$Using.class
+ org/springframework/boot/loader/launch/PropertiesLauncher$Instantiator.class
+ org/springframework/boot/loader/launch/PropertiesLauncher.class
+ org/springframework/boot/loader/launch/SystemPropertyUtils.class
+ org/springframework/boot/loader/launch/WarLauncher.class
+ org/springframework/boot/loader/log/
+ org/springframework/boot/loader/log/DebugLogger$DisabledDebugLogger.class
+ org/springframework/boot/loader/log/DebugLogger$SystemErrDebugLogger.class
+ org/springframework/boot/loader/log/DebugLogger.class
+ org/springframework/boot/loader/net/
+ org/springframework/boot/loader/net/protocol/
+ org/springframework/boot/loader/net/protocol/Handlers.class
+ org/springframework/boot/loader/net/protocol/jar/
+ org/springframework/boot/loader/net/protocol/jar/Canonicalizer.class
+ org/springframework/boot/loader/net/protocol/jar/Handler.class
+ org/springframework/boot/loader/net/protocol/jar/JarFileUrlKey.class
+ org/springframework/boot/loader/net/protocol/jar/JarUrl.class
+ org/springframework/boot/loader/net/protocol/jar/JarUrlClassLoader$OptimizedEnumeration.class
+ org/springframework/boot/loader/net/protocol/jar/JarUrlClassLoader.class
+ org/springframework/boot/loader/net/protocol/jar/JarUrlConnection$ConnectionInputStream.class
+ org/springframework/boot/loader/net/protocol/jar/JarUrlConnection$EmptyUrlStreamHandler.class
+ org/springframework/boot/loader/net/protocol/jar/JarUrlConnection.class
+ org/springframework/boot/loader/net/protocol/jar/LazyDelegatingInputStream.class
+ org/springframework/boot/loader/net/protocol/jar/Optimizations.class
+ org/springframework/boot/loader/net/protocol/jar/UrlJarEntry.class
+ org/springframework/boot/loader/net/protocol/jar/UrlJarFile.class
+ org/springframework/boot/loader/net/protocol/jar/UrlJarFileFactory.class
+ org/springframework/boot/loader/net/protocol/jar/UrlJarFiles$Cache.class
+ org/springframework/boot/loader/net/protocol/jar/UrlJarFiles.class
+ org/springframework/boot/loader/net/protocol/jar/UrlJarManifest$ManifestSupplier.class
+ org/springframework/boot/loader/net/protocol/jar/UrlJarManifest.class
+ org/springframework/boot/loader/net/protocol/jar/UrlNestedJarFile.class
+ org/springframework/boot/loader/net/protocol/nested/
+ org/springframework/boot/loader/net/protocol/nested/Handler.class
+ org/springframework/boot/loader/net/protocol/nested/NestedLocation.class
+ org/springframework/boot/loader/net/protocol/nested/NestedUrlConnection$ConnectionInputStream.class
+ org/springframework/boot/loader/net/protocol/nested/NestedUrlConnection.class
+ org/springframework/boot/loader/net/protocol/nested/NestedUrlConnectionResources.class
+ org/springframework/boot/loader/net/util/
+ org/springframework/boot/loader/net/util/UrlDecoder.class
+ org/springframework/boot/loader/nio/
+ org/springframework/boot/loader/nio/file/
+ org/springframework/boot/loader/nio/file/NestedByteChannel$Resources.class
+ org/springframework/boot/loader/nio/file/NestedByteChannel.class
+ org/springframework/boot/loader/nio/file/NestedFileStore.class
+ org/springframework/boot/loader/nio/file/NestedFileSystem.class
+ org/springframework/boot/loader/nio/file/NestedFileSystemProvider.class
+ org/springframework/boot/loader/nio/file/NestedPath.class
+ org/springframework/boot/loader/nio/file/UriPathEncoder.class
+ org/springframework/boot/loader/ref/
+ org/springframework/boot/loader/ref/Cleaner.class
+ org/springframework/boot/loader/ref/DefaultCleaner.class
+ org/springframework/boot/loader/zip/
+ org/springframework/boot/loader/zip/ByteArrayDataBlock.class
+ org/springframework/boot/loader/zip/CloseableDataBlock.class
+ org/springframework/boot/loader/zip/DataBlock.class
+ org/springframework/boot/loader/zip/DataBlockInputStream.class
+ org/springframework/boot/loader/zip/FileDataBlock$FileAccess.class
+ org/springframework/boot/loader/zip/FileDataBlock$Tracker$1.class
+ org/springframework/boot/loader/zip/FileDataBlock$Tracker.class
+ org/springframework/boot/loader/zip/FileDataBlock.class
+ org/springframework/boot/loader/zip/NameOffsetLookups.class
+ org/springframework/boot/loader/zip/VirtualDataBlock.class
+ org/springframework/boot/loader/zip/VirtualZipDataBlock$DataPart.class
+ org/springframework/boot/loader/zip/VirtualZipDataBlock.class
+ org/springframework/boot/loader/zip/Zip64EndOfCentralDirectoryLocator.class
+ org/springframework/boot/loader/zip/Zip64EndOfCentralDirectoryRecord.class
+ org/springframework/boot/loader/zip/ZipCentralDirectoryFileHeaderRecord.class
+ org/springframework/boot/loader/zip/ZipContent$Entry.class
+ org/springframework/boot/loader/zip/ZipContent$Kind.class
+ org/springframework/boot/loader/zip/ZipContent$Loader.class
+ org/springframework/boot/loader/zip/ZipContent$Source.class
+ org/springframework/boot/loader/zip/ZipContent.class
+ org/springframework/boot/loader/zip/ZipDataDescriptorRecord.class
+ org/springframework/boot/loader/zip/ZipEndOfCentralDirectoryRecord$Located.class
+ org/springframework/boot/loader/zip/ZipEndOfCentralDirectoryRecord.class
+ org/springframework/boot/loader/zip/ZipLocalFileHeaderRecord.class
+ org/springframework/boot/loader/zip/ZipString$CompareType.class
+ org/springframework/boot/loader/zip/ZipString.class
+ """;
+ private static final String MAIN_CLASS = "com.example.Foo";
+ private static final String PROVIDED_LIB = "LatencyUtils-2.0.3.jar";
+ private static final String SPRING_BOOT = "spring-boot-" + BOOT_VERSION + ".jar";
+ private static final String SPRING_BOOT_ACTUATOR = "spring-boot-actuator-" + BOOT_VERSION + ".jar";
+ private static final String SPRING_BOOT_LOADER = "spring-boot-loader-" + BOOT_VERSION + ".jar";
+ private static final String SRC_MAIN_JAVA = "src/main/java";
+ private static final String SRC_TEST_JAVA = "src/test/java";
+
+ private StringBuilder readJarEntries(File jar) throws IOException {
+ var jarEntries = new StringBuilder();
+ try (var jarFile = new JarFile(jar)) {
+ for (Enumeration entries = jarFile.entries(); entries.hasMoreElements(); ) {
+ jarEntries.append(entries.nextElement().getName()).append('\n');
+ }
+ }
+ return jarEntries;
+ }
+
+ static class CustomProject extends Project {
+ CustomProject(File tmp) {
+ super();
+ workDirectory = tmp;
+ pkg = "com.example";
+ name = "test_project";
+ version = new VersionNumber(0, 0, 1);
+ mainClass = MAIN_CLASS;
+ }
+ }
+
+ @Nested
+ @DisplayName("Errors Tests")
+ class ErrorsTests {
+ @Test
+ void launcherClass() throws IOException {
+ var bootWar = new BootJarOperation().mainClass(MAIN_CLASS)
+ .launcherClass("org.springframework.boot.loader.launch.WarLauncher")
+ .launcherLibs(List.of(new File(EXAMPLES_LIB_STANDALONE + SPRING_BOOT_LOADER)));
+ assertThat(bootWar.verifyExecute()).isTrue();
+ }
+
+ @Test
+ void misingLauncherLibs() {
+ assertThatCode(() -> new BootWarOperation().launcherLibs(new File("foo")))
+ .as("foo")
+ .isInstanceOf(IOException.class)
+ .hasMessageContaining("not found");
+
+ assertThatCode(() -> new BootWarOperation().launcherLibs("bar"))
+ .as("bar")
+ .isInstanceOf(IOException.class)
+ .hasMessageContaining("not found");
+ }
+
+ @Test
+ void missingLauncherClass() throws IOException {
+ var bootWar = new BootWarOperation().mainClass(MAIN_CLASS)
+ .launcherLibs(List.of(new File(EXAMPLES_LIB_STANDALONE + SPRING_BOOT_LOADER)));
+ assertThatCode(bootWar::execute)
+ .isInstanceOf((IllegalArgumentException.class))
+ .hasMessageContaining("class required");
+ }
+
+ @Test
+ void missingOrInvalidMainClass() {
+ var bootWar = new BootWarOperation();
+ assertThatCode(bootWar::execute).isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("mainClass");
+
+ bootWar = bootWar.mainClass(MAIN_CLASS);
+ assertThatCode(bootWar::execute)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("class required");
+ }
+ }
+
+ @Nested
+ @DisplayName("Libs Tests")
+ class LibsTests {
+ @Nested
+ @DisplayName("Inf Lib Tests")
+ class InfLibTest {
+ private final File bar = new File(EXAMPLES_LIB_COMPILE + SPRING_BOOT_ACTUATOR);
+ private final File foo = new File(EXAMPLES_LIB_COMPILE + SPRING_BOOT);
+ private final BootWarOperation op = new BootWarOperation();
+
+ @Test
+ void infLibsAsFileArray() {
+ op.infLibs().clear();
+ op.infLibs(foo, bar);
+ assertThat(op.infLibs()).as("File...").containsExactly(foo, bar);
+ }
+
+ @Test
+ void infLibsAsFileList() {
+ op.infLibs().clear();
+ op.infLibs(List.of(foo, bar));
+ assertThat(op.infLibs()).as("List(File...)").containsExactly(foo, bar);
+
+ }
+
+ @Test
+ void infLibsAsPathArray() {
+ op.infLibs().clear();
+ op.infLibs(foo.toPath(), bar.toPath());
+ assertThat(op.infLibs()).as("Path...").containsExactly(foo, bar);
+ }
+
+ @Test
+ void infLibsAsPathList() {
+ op.infLibs().clear();
+ op.infLibsPaths(List.of(foo.toPath(), bar.toPath()));
+ assertThat(op.infLibs()).as("List(Path...)").containsExactly(foo, bar);
+ }
+
+ @Test
+ void infLibsAsStringArray() {
+ op.infLibs().clear();
+ op.infLibs(EXAMPLES_LIB_COMPILE + SPRING_BOOT, EXAMPLES_LIB_COMPILE + SPRING_BOOT_ACTUATOR);
+ assertThat(op.infLibs()).as("String...").containsExactly(foo, bar);
+ }
+
+ @Test
+ void infLibsAsStringList() {
+ op.infLibs().clear();
+ op.infLibsStrings(List.of(EXAMPLES_LIB_COMPILE + SPRING_BOOT, EXAMPLES_LIB_COMPILE + SPRING_BOOT_ACTUATOR));
+ assertThat(op.infLibs()).as("List(String...)").containsExactly(foo, bar);
+
+ }
+ }
+
+ @Nested
+ @DisplayName("Launcher Libs Tests")
+ class LauncherLibTests {
+ private final File launcher = new File(EXAMPLES_LIB_STANDALONE + SPRING_BOOT_LOADER);
+ private final BootJarOperation op = new BootJarOperation();
+
+ @Test
+ void launcherLibsAsFileArray() throws IOException {
+ op.launcherLibs().clear();
+ op.launcherLibs(launcher);
+ assertThat(op.launcherLibs()).as("File...").containsExactly(launcher);
+ }
+
+ @Test
+ void launcherLibsAsFileList() throws IOException {
+ op.launcherLibs().clear();
+ op.launcherLibs(List.of(launcher));
+ assertThat(op.launcherLibs()).as("List(File...)").containsExactly(launcher);
+ }
+
+ @Test
+ void launcherLibsAsPathArray() throws IOException {
+ op.launcherLibs().clear();
+ op.launcherLibs(launcher.toPath());
+ assertThat(op.launcherLibs()).as("Path...").containsExactly(launcher);
+
+ op.launcherLibs().clear();
+ op.launcherLibsStrings(List.of(EXAMPLES_LIB_STANDALONE + SPRING_BOOT_LOADER));
+ assertThat(op.launcherLibs()).as("List(String...)").containsExactly(launcher);
+ }
+
+ @Test
+ void launcherLibsAsPathList() throws IOException {
+ op.launcherLibs().clear();
+ op.launcherLibsPaths(List.of(launcher.toPath()));
+ assertThat(op.launcherLibs()).as("List(Path...)").containsExactly(launcher);
+ }
+
+ @Test
+ void launcherLibsAsStringArray() throws IOException {
+ op.launcherLibs().clear();
+ op.launcherLibs(EXAMPLES_LIB_STANDALONE + SPRING_BOOT_LOADER);
+ assertThat(op.launcherLibs()).as("String...").containsExactly(launcher);
+ }
+ }
+
+ @Nested
+ @DisplayName("War Libs Tests")
+ class WarLibTest {
+ private final File foo = new File(EXAMPLES_LIB_RUNTIME + PROVIDED_LIB);
+ private final BootWarOperation op = new BootWarOperation();
+
+ @Test
+ void warProvidedLibs() {
+ op.providedLibs().clear();
+ op.providedLibs(foo);
+ assertThat(op.providedLibs()).containsExactly(foo);
+ }
+
+ @Test
+ void warProvidedLibsAsPath() {
+ op.providedLibs().clear();
+ op.providedLibs(foo.toPath());
+ assertThat(op.providedLibs()).containsExactly(foo);
+ }
+
+ @Test
+ void warProvidedLibsAsString() {
+ op.providedLibs().clear();
+ op.providedLibs(EXAMPLES_LIB_RUNTIME + PROVIDED_LIB);
+ assertThat(op.providedLibs()).containsExactly(foo);
+ }
+ }
+ }
+
+ @Nested
+ @DisplayName("Project Tests")
+ class ProjectTests {
+ @Test
+ void customProject() throws IOException {
+ var tmpDir = Files.createTempDirectory("boot-prj-tmp-").toFile();
+ var project = new CustomProject(tmpDir);
+ var bootJar = new BootJarOperation().fromProject(project).sourceDirectories(SRC_MAIN_JAVA);
+
+ try (var softly = new AutoCloseableSoftAssertions()) {
+ softly.assertThat(bootJar.mainClass()).as("mainClass").isEqualTo(MAIN_CLASS);
+ softly.assertThat(bootJar.sourceDirectories()).as("sourceDirectories.size").hasSize(3)
+ .containsExactly(project.buildMainDirectory(), project.srcMainResourcesDirectory(),
+ new File(SRC_MAIN_JAVA));
+ softly.assertThat(bootJar.manifestAttributes()).as("manifestAttributes.size").hasSize(3);
+ softly.assertThat(bootJar.manifestAttributes().get("Manifest-Version")).as("Manifest-Version")
+ .isEqualTo("1.0");
+ softly.assertThat(bootJar.manifestAttributes().get("Main-Class")).as("Main-Class").endsWith("JarLauncher");
+ softly.assertThat(bootJar.manifestAttributes().get("Start-Class")).as("Start-Class").isEqualTo(MAIN_CLASS);
+ softly.assertThat(bootJar.manifestAttribute("Manifest-Test", "tsst")
+ .manifestAttributes().get("Manifest-Test")).as("Manifest-Test").isEqualTo("tsst");
+ softly.assertThat(bootJar.destinationDirectory()).as("destinationDirectory").isDirectory();
+ softly.assertThat(bootJar.destinationDirectory()).isEqualTo(project.buildDistDirectory());
+ softly.assertThat(bootJar.infLibs()).as("infoLibs").isEmpty();
+ softly.assertThat(bootJar.launcherLibs()).as("launcherJars").isEmpty();
+ softly.assertThat(bootJar.destinationFileName()).isEqualTo("test_project-0.0.1-boot.jar");
+ }
+
+ FileUtils.deleteDirectory(tmpDir);
+ }
+
+ @Test
+ @SuppressWarnings("PMD.AvoidDuplicateLiterals")
+ void jarExecute() throws Exception {
+ var tmpDir = Files.createTempDirectory("bootjartmp").toFile();
+ var jar = "foo-1.1.1.jar";
+ new BootJarOperation()
+ .launcherClass("org.springframework.boot.loader.launch.JarLauncher")
+ .launcherLibs(List.of(new File(EXAMPLES_LIB_STANDALONE + SPRING_BOOT_LOADER)))
+ .destinationDirectory(tmpDir)
+ .destinationFileName(jar)
+ .infLibs(new File(EXAMPLES_LIB_COMPILE + SPRING_BOOT),
+ new File(EXAMPLES_LIB_COMPILE + SPRING_BOOT_ACTUATOR))
+ .mainClass(MAIN_CLASS)
+ .sourceDirectories(new File("build/main"))
+ .execute();
+
+ var jarFile = new File(tmpDir, jar);
+ assertThat(jarFile).exists();
+
+ var jarEntries = readJarEntries(jarFile);
+ assertThat(jarEntries).isEqualToIgnoringNewLines(
+ "BOOT-INF/\n" +
+ "BOOT-INF/classes/\n" +
+ "BOOT-INF/classes/rife/\n" +
+ "BOOT-INF/classes/rife/bld/\n" +
+ "BOOT-INF/classes/rife/bld/extension/\n" +
+ "BOOT-INF/classes/rife/bld/extension/AbstractBootOperation.class\n" +
+ "BOOT-INF/classes/rife/bld/extension/BootJarOperation.class\n" +
+ "BOOT-INF/classes/rife/bld/extension/BootUtils.class\n" +
+ "BOOT-INF/classes/rife/bld/extension/BootWarOperation.class\n" +
+ "BOOT-INF/lib/\n" +
+ "BOOT-INF/lib/" + SPRING_BOOT + '\n' +
+ "BOOT-INF/lib/" + SPRING_BOOT_ACTUATOR + '\n' +
+ "META-INF/\n" +
+ "META-INF/MANIFEST.MF\n" + LAUNCHER_JARS);
+
+ FileUtils.deleteDirectory(tmpDir);
+ }
+
+ @Test
+ void jarProjectExecute() throws Exception {
+ var tmpDir = Files.createTempDirectory("boot-war-tmp-").toFile();
+ new BootJarOperation()
+ .fromProject(new CustomProject(new File(".")))
+ .launcherLibs(List.of(new File(EXAMPLES_LIB_STANDALONE + SPRING_BOOT_LOADER)))
+ .destinationDirectory(tmpDir.getAbsolutePath())
+ .infLibs(new File(EXAMPLES_LIB_COMPILE + SPRING_BOOT).getAbsolutePath(),
+ new File(EXAMPLES_LIB_COMPILE + SPRING_BOOT_ACTUATOR).getAbsolutePath())
+ .execute();
+
+ var jarFile = new File(tmpDir, "test_project-0.0.1-boot.jar");
+ assertThat(jarFile).exists();
+
+ var jarEntries = readJarEntries(jarFile);
+ assertThat(jarEntries).isEqualToIgnoringNewLines(
+ "BOOT-INF/\n" +
+ "BOOT-INF/classes/\n" +
+ "BOOT-INF/classes/rife/\n" +
+ "BOOT-INF/classes/rife/bld/\n" +
+ "BOOT-INF/classes/rife/bld/extension/\n" +
+ "BOOT-INF/classes/rife/bld/extension/AbstractBootOperation.class\n" +
+ "BOOT-INF/classes/rife/bld/extension/BootJarOperation.class\n" +
+ "BOOT-INF/classes/rife/bld/extension/BootUtils.class\n" +
+ "BOOT-INF/classes/rife/bld/extension/BootWarOperation.class\n" +
+ "BOOT-INF/lib/\n" +
+ "BOOT-INF/lib/" + BLD + '\n' +
+ "BOOT-INF/lib/" + SPRING_BOOT + '\n' +
+ "BOOT-INF/lib/" + SPRING_BOOT_ACTUATOR + '\n' +
+ "META-INF/\n" +
+ "META-INF/MANIFEST.MF\n" + LAUNCHER_JARS);
+
+ FileUtils.deleteDirectory(tmpDir);
+ }
+
+ @Test
+ void warProjectExecute() throws Exception {
+ var tmpDir = Files.createTempDirectory("bootjartmp").toFile();
+ var project = new CustomProject(new File("."));
+ new BootWarOperation()
+ .fromProject(project)
+ .launcherLibs(List.of(new File(EXAMPLES_LIB_STANDALONE + SPRING_BOOT_LOADER)))
+ .destinationDirectory(tmpDir.toPath())
+ .infLibs(Path.of(EXAMPLES_LIB_COMPILE + SPRING_BOOT),
+ Path.of(EXAMPLES_LIB_COMPILE + SPRING_BOOT_ACTUATOR))
+ .providedLibs(new File(EXAMPLES_LIB_RUNTIME + PROVIDED_LIB))
+ .execute();
+
+ var warFile = new File(tmpDir, project.name() + '-' + project.version().toString() + "-boot.war");
+ assertThat(warFile).exists();
+
+ var jarEntries = readJarEntries(warFile);
+ assertThat(jarEntries).isEqualToIgnoringNewLines(
+ "META-INF/\n" +
+ "META-INF/MANIFEST.MF\n" +
+ "WEB-INF/\n" +
+ "WEB-INF/classes/\n" +
+ "WEB-INF/classes/rife/\n" +
+ "WEB-INF/classes/rife/bld/\n" +
+ "WEB-INF/classes/rife/bld/extension/\n" +
+ "WEB-INF/classes/rife/bld/extension/AbstractBootOperation.class\n" +
+ "WEB-INF/classes/rife/bld/extension/BootJarOperation.class\n" +
+ "WEB-INF/classes/rife/bld/extension/BootUtils.class\n" +
+ "WEB-INF/classes/rife/bld/extension/BootWarOperation.class\n" +
+ "WEB-INF/lib/\n" +
+ "WEB-INF/lib/" + BLD + '\n' +
+ "WEB-INF/lib/dist/\n" +
+ "WEB-INF/lib/" + SPRING_BOOT + '\n' +
+ "WEB-INF/lib/" + SPRING_BOOT_ACTUATOR + '\n' +
+ "WEB-INF/lib-provided/\n" +
+ "WEB-INF/lib-provided/" + PROVIDED_LIB + '\n' + LAUNCHER_JARS);
+
+ FileUtils.deleteDirectory(tmpDir);
+ }
+ }
+
+ @Nested
+ @DisplayName("Source Directories Tests")
+ class SourceDirectoriesTests {
+ public final BootJarOperation op = new BootJarOperation();
+ public final File src = new File(SRC_MAIN_JAVA);
+ public final File test = new File(SRC_TEST_JAVA);
+
+ @Test
+ void sourceDirectoriesAsFileArray() {
+ op.sourceDirectories().clear();
+ op.sourceDirectories(src, test);
+ assertThat(op.sourceDirectories()).as("File...").containsExactly(src, test);
+ }
+
+ @Test
+ void sourceDirectoriesAsFileList() {
+ op.sourceDirectories().clear();
+ op.sourceDirectories(List.of(src, test));
+ assertThat(op.sourceDirectories()).as("List(File...)").containsExactly(src, test);
+ }
+
+ @Test
+ void sourceDirectoriesAsPathArray() {
+ op.sourceDirectories().clear();
+ op.sourceDirectories(src.toPath(), test.toPath());
+ assertThat(op.sourceDirectories()).as("Path...").containsExactly(src, test);
+ }
+
+ @Test
+ void sourceDirectoriesAsPathList() {
+ op.sourceDirectories().clear();
+ op.sourceDirectoriesPaths(List.of(src.toPath(), test.toPath()));
+ assertThat(op.sourceDirectories()).as("List(Path...)").containsExactly(src, test);
+ }
+
+ @Test
+ void sourceDirectoriesAsStringArray() {
+ op.sourceDirectories().clear();
+ op.sourceDirectories(SRC_MAIN_JAVA, SRC_TEST_JAVA);
+ assertThat(op.sourceDirectories()).as("String...").containsExactly(src, test);
+ }
+
+ @Test
+ void sourceDirectoriesAsStringList() {
+ op.sourceDirectories().clear();
+ op.sourceDirectoriesStrings(List.of(SRC_MAIN_JAVA, SRC_TEST_JAVA));
+ assertThat(op.sourceDirectories()).as("List(String...").containsExactly(src, test);
+ }
+ }
+}
diff --git a/src/test/java/rife/bld/extension/BootUtilsTest.java b/src/test/java/rife/bld/extension/BootUtilsTest.java
new file mode 100644
index 0000000..f93efcc
--- /dev/null
+++ b/src/test/java/rife/bld/extension/BootUtilsTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright 2023-2025 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 rife.bld.extension;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import rife.bld.blueprints.BaseProjectBlueprint;
+import rife.tools.FileUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class BootUtilsTest {
+ @Nested
+ @DisplayName("FileSize Tests")
+ class FileSizeTests {
+ private static final String TMP_FILE_PREFIX = "filesize-tmp-";
+
+ @Test
+ void fileSizeInBytes() throws IOException {
+ var file = File.createTempFile(TMP_FILE_PREFIX, null);
+ file.deleteOnExit();
+
+ Files.write(file.toPath(), "1234".getBytes());
+ assertEquals("4 B", BootUtils.fileSize(file));
+ }
+
+ @Test
+ void fileSizeInGigabytes() {
+ // This test simulates a 3 GB file by mocking the length method
+ var file = new File("testFile") {
+ @Override
+ public long length() {
+ return 3L * 1024 * 1024 * 1024; // 3 GB
+ }
+ };
+ assertEquals("3 GB", BootUtils.fileSize(file));
+ }
+
+ @Test
+ void fileSizeInKilobytes() throws IOException {
+ var file = File.createTempFile(TMP_FILE_PREFIX, null);
+ file.deleteOnExit();
+
+ var data = new byte[1500]; // ~1.46 KB
+ Files.write(file.toPath(), data);
+
+ assertEquals("1.5 KB", BootUtils.fileSize(file));
+ }
+
+ @Test
+ void fileSizeInMegabytes() throws IOException {
+ var file = File.createTempFile(TMP_FILE_PREFIX, null);
+ file.deleteOnExit();
+
+ var data = new byte[5 * 1024 * 1024]; // 5 MB
+ Files.write(file.toPath(), data);
+
+ assertEquals("5 MB", BootUtils.fileSize(file));
+ }
+
+ @Test
+ void fileSizeZeroBytes() throws IOException {
+ var file = File.createTempFile(TMP_FILE_PREFIX, null);
+ file.deleteOnExit();
+ assertEquals("0 B", BootUtils.fileSize(file));
+ }
+ }
+
+ @Nested
+ @DisplayName("LauncherClass Tests")
+ class LauncherClassTests {
+ private final File examplesProjectDir = new File("examples");
+
+ @Test
+ void jarLauncher() {
+ var launcher = BootUtils.launcherClass(new BaseProjectBlueprint(
+ examplesProjectDir, "com.example", "examples", "Examples"), "JarLauncher");
+ assertEquals("org.springframework.boot.loader.JarLauncher", launcher);
+ }
+
+ @Test
+ void warLauncher() {
+ var launcher = BootUtils.launcherClass(new BaseProjectBlueprint(
+ examplesProjectDir, "com.example", "examples", "Examples"), "WarLauncher");
+ assertEquals("org.springframework.boot.loader.WarLauncher", launcher);
+ }
+ }
+
+ @Nested
+ @DisplayName("MkDirs Tests")
+ class MkDirsTests {
+ @Test
+ void mkDirsCreatesDirectories() throws IOException {
+ var tmpDir = File.createTempFile("mkdirs-tmp-", null);
+ FileUtils.deleteDirectory(tmpDir);
+ assertFalse(tmpDir.exists());
+
+ BootUtils.mkDirs(tmpDir);
+ assertTrue(tmpDir.exists());
+
+ FileUtils.deleteDirectory(tmpDir);
+ }
+
+ @Test
+ void mkDirsThrowsIOException() {
+ var tmpFile = new File("/foo/bar.txt");
+ tmpFile.deleteOnExit();
+
+ var exception = assertThrows(IOException.class, () -> BootUtils.mkDirs(tmpFile));
+ assertEquals("Unable to create: " + tmpFile.getAbsolutePath(), exception.getMessage());
+ }
+ }
+}
\ No newline at end of file