From da448d7acdb6ca8fba9c3e09638b36f32b1874c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juraj=20Hrivn=C3=A1k?= Date: Sun, 13 Oct 2024 01:55:23 +0200 Subject: [PATCH 1/5] Refactor glob to improve performance - Still it's too slow with large modpacks --- .../kotlin/teksturepako/pakku/Version.kt | 2 +- .../pakku/api/actions/export/Export.kt | 56 ++++++++++++------ .../teksturepako/pakku/api/data/ConfigFile.kt | 27 ++++++--- .../kotlin/teksturepako/pakku/io/Glob.kt | 57 ++++++++++++++----- 4 files changed, 103 insertions(+), 39 deletions(-) diff --git a/src/commonMain/kotlin/teksturepako/pakku/Version.kt b/src/commonMain/kotlin/teksturepako/pakku/Version.kt index 075a328f..76b02040 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/Version.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/Version.kt @@ -2,4 +2,4 @@ package teksturepako.pakku -const val VERSION = "0.19.0" +const val VERSION = "0.20.0" diff --git a/src/commonMain/kotlin/teksturepako/pakku/api/actions/export/Export.kt b/src/commonMain/kotlin/teksturepako/pakku/api/actions/export/Export.kt index b7ab0690..e1104d18 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/api/actions/export/Export.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/api/actions/export/Export.kt @@ -268,43 +268,67 @@ suspend fun List.finishResults( suspend fun List.produceRuleResults( onError: suspend (error: ActionError) -> Unit, lockFile: LockFile, configFile: ConfigFile, workingSubDir: String -): List -{ - val results = this.fold(listOf>()) { acc, rule -> - acc + lockFile.getAllProjects().map { - rule to RuleContext.ExportingProject(it, lockFile, configFile, workingSubDir) - } + configFile.getAllOverrides().mapNotNull { result -> +): List = coroutineScope { + val projects: Deferred> = async { + lockFile.getAllProjects().map { + RuleContext.ExportingProject(it, lockFile, configFile, workingSubDir) + } + } + + val overrides: Deferred> = async { + configFile.getAllOverrides().await().mapNotNull { result -> result.resultFold( success = { - rule to RuleContext.ExportingOverride(it, OverrideType.OVERRIDE, lockFile, configFile, workingSubDir) + RuleContext.ExportingOverride(it, OverrideType.OVERRIDE, lockFile, configFile, workingSubDir) }, failure = { onError(it) null } ) - } + configFile.getAllServerOverrides().mapNotNull { result -> + } + } + + val serverOverrides: Deferred> = async { + configFile.getAllServerOverrides().await().mapNotNull { result -> result.resultFold( success = { - rule to RuleContext.ExportingOverride(it, OverrideType.SERVER_OVERRIDE, lockFile, configFile, workingSubDir) + RuleContext.ExportingOverride(it, OverrideType.SERVER_OVERRIDE, lockFile, configFile, workingSubDir) }, failure = { onError(it) null } ) - } + configFile.getAllClientOverrides().mapNotNull { result -> + } + } + + val clientOverrides: Deferred> = async { + configFile.getAllClientOverrides().await().mapNotNull { result -> result.resultFold( success = { - rule to RuleContext.ExportingOverride(it, OverrideType.CLIENT_OVERRIDE, lockFile, configFile, workingSubDir) + RuleContext.ExportingOverride(it, OverrideType.CLIENT_OVERRIDE, lockFile, configFile, workingSubDir) }, failure = { onError(it) null } ) - } + readProjectOverrides(configFile).map { - rule to RuleContext.ExportingProjectOverride(it, lockFile, configFile, workingSubDir) + } + } + + val projectOverrides: Deferred> = async { + readProjectOverrides(configFile).map { + RuleContext.ExportingProjectOverride(it, lockFile, configFile, workingSubDir) + } + } + + val deferredContexts = listOf(projects, overrides, serverOverrides, clientOverrides, projectOverrides) + val contexts = deferredContexts.awaitAll().flatten() + + val results = this@produceRuleResults.fold(listOf>()) { acc, rule -> + acc + contexts.map { context -> + rule to context } }.map { (exportRule, ruleContext) -> exportRule.getResult(ruleContext) @@ -313,15 +337,15 @@ suspend fun List.produceRuleResults( val missing = results.filter { it.ruleContext is RuleContext.MissingProject }.flatMap { ruleResult -> - this.map { rule -> + this@produceRuleResults.map { rule -> val project = (ruleResult.ruleContext as RuleContext.MissingProject).project rule.getResult(RuleContext.MissingProject(project, lockFile, configFile, workingSubDir)) } } - val finished = this.map { rule -> + val finished = this@produceRuleResults.map { rule -> rule.getResult(Finished(lockFile, configFile, workingSubDir)) } - return results + missing + finished + return@coroutineScope results + missing + finished } diff --git a/src/commonMain/kotlin/teksturepako/pakku/api/data/ConfigFile.kt b/src/commonMain/kotlin/teksturepako/pakku/api/data/ConfigFile.kt index 6f1c69c9..5fecc888 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/api/data/ConfigFile.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/api/data/ConfigFile.kt @@ -3,6 +3,9 @@ package teksturepako.pakku.api.data import com.github.michaelbull.result.* +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import teksturepako.pakku.api.actions.ActionError @@ -100,17 +103,23 @@ data class ConfigFile( this.overrides.clear() } - fun getAllOverrides(): List> = this.overrides - .expandWithGlob(Path(workingPath)) - .map { filterPath(it) } + suspend fun getAllOverrides(): Deferred>> = coroutineScope { + async { + this@ConfigFile.overrides.expandWithGlob(Path(workingPath)).map { filterPath(it) } + } + } - fun getAllServerOverrides(): List> = this.serverOverrides - .expandWithGlob(Path(workingPath)) - .map { filterPath(it) } + suspend fun getAllServerOverrides(): Deferred>> = coroutineScope { + async { + this@ConfigFile.serverOverrides.expandWithGlob(Path(workingPath)).map { filterPath(it) } + } + } - fun getAllClientOverrides(): List> = this.clientOverrides - .expandWithGlob(Path(workingPath)) - .map { filterPath(it) } + suspend fun getAllClientOverrides(): Deferred>> = coroutineScope { + async { + this@ConfigFile.clientOverrides.expandWithGlob(Path(workingPath)).map { filterPath(it) } + } + } // -- PROJECTS -- diff --git a/src/commonMain/kotlin/teksturepako/pakku/io/Glob.kt b/src/commonMain/kotlin/teksturepako/pakku/io/Glob.kt index 9a5fac80..8fdd4997 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/io/Glob.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/io/Glob.kt @@ -1,29 +1,60 @@ package teksturepako.pakku.io +import com.github.michaelbull.result.get +import com.github.michaelbull.result.runCatching +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import teksturepako.pakku.debug import teksturepako.pakku.debugIf import java.io.File import java.nio.file.FileSystems import java.nio.file.Path +import java.nio.file.PathMatcher import kotlin.io.path.* @OptIn(ExperimentalPathApi::class) -fun Path.listDirectoryEntriesRecursive(glob: String): Sequence -{ - val globPattern = "glob:${this.invariantSeparatorsPathString}/${glob.removePrefix("./")}" - val matcher = FileSystems.getDefault().getPathMatcher(globPattern) +suspend fun Path.walk(inputGlobs: List): Sequence> = withContext(Dispatchers.IO) { + val matchers: List> = inputGlobs.mapNotNull { input -> + val negating = input.startsWith("!") + val glob = (if (negating) input.removePrefix("!") else input).removePrefix("./") + + val globPattern = "glob:${this@walk.invariantSeparatorsPathString}/$glob" + runCatching { FileSystems.getDefault().getPathMatcher(globPattern) to negating }.get() + } + + return@withContext this@walk.walk(PathWalkOption.INCLUDE_DIRECTORIES) + .mapNotNull { path -> + val matcher = matchers.filter { (matcher) -> matcher.matches(path) } - return this.walk(PathWalkOption.INCLUDE_DIRECTORIES) - .filter { matcher.matches(it) } - .map { Path(it.absolutePathString().removePrefix(this.absolutePathString()).removePrefix(File.separator)) } + if (matcher.isEmpty()) return@mapNotNull null + + matcher to path + }.map { (matchers, path) -> + Path(path.absolutePathString().removePrefix(this@walk.absolutePathString()).removePrefix(File.separator)) to matchers.any { it.second } + } } -fun List.expandWithGlob(inputPath: Path) = fold(listOf()) { acc, glob -> - if (glob.startsWith("!")) +suspend fun List.expandWithGlob(inputPath: Path): List +{ + val paths = mutableSetOf() + + val walk = inputPath.walk(this).toList().debug { + for ((path, negating) in it) + { + println("${if (negating) "!" else ""}$path") + } + } + + for ((path, _) in walk) { - acc - inputPath.listDirectoryEntriesRecursive(glob.removePrefix("!")).map { it.toString() }.toSet() + paths += path.toString() } - else + + for ((path, negating) in walk) { - acc + inputPath.listDirectoryEntriesRecursive(glob).map { it.toString() } + if (negating) paths -= path.toString() } -}.debugIf({ it.isNotEmpty() }) { println("expandWithGlob = $it") } + + return paths.toList().debugIf({ it.isNotEmpty() }) { println("expandWithGlob = $it") } +} + From 31ec8798197de86de55e193f367531faf585866b Mon Sep 17 00:00:00 2001 From: Juraj Hrivnak Date: Mon, 16 Dec 2024 16:47:12 +0100 Subject: [PATCH 2/5] Reduce number of glob pattern lookups --- .../pakku/api/actions/export/Export.kt | 45 ++++++++++++++----- .../teksturepako/pakku/api/data/ConfigFile.kt | 12 ++--- 2 files changed, 39 insertions(+), 18 deletions(-) diff --git a/src/commonMain/kotlin/teksturepako/pakku/api/actions/export/Export.kt b/src/commonMain/kotlin/teksturepako/pakku/api/actions/export/Export.kt index ef4888f2..bd2dc50b 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/api/actions/export/Export.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/api/actions/export/Export.kt @@ -1,5 +1,6 @@ package teksturepako.pakku.api.actions.export +import com.github.michaelbull.result.Result import com.github.michaelbull.result.get import com.github.michaelbull.result.onFailure import com.github.michaelbull.result.runCatching @@ -46,12 +47,24 @@ suspend fun export( configFile: ConfigFile, platforms: List ): List = coroutineScope { + val overrides: Deferred>> = async { + configFile.getAllOverrides() + } + + val serverOverrides: Deferred>> = async { + configFile.getAllServerOverrides() + } + + val clientOverrides: Deferred>> = async { + configFile.getAllClientOverrides() + } + profiles.map { profile -> launch { profile.export( onError = { profile, error -> onError(profile, error) }, onSuccess = { profile, path, duration -> onSuccess(profile, path, duration) }, - lockFile, configFile, platforms + lockFile, configFile, platforms, overrides, serverOverrides, clientOverrides ) } } @@ -62,7 +75,10 @@ suspend fun ExportProfile.export( onSuccess: suspend (profile: ExportProfile, path: Path, duration: Duration) -> Unit, lockFile: LockFile, configFile: ConfigFile, - platforms: List + platforms: List, + overrides: Deferred>>, + serverOverrides: Deferred>>, + clientOverrides: Deferred>>, ) { if (this.dependsOn != null && this.dependsOn !in platforms) return @@ -88,8 +104,10 @@ suspend fun ExportProfile.export( val inputDirectory = Path(cacheDir.pathString, this.name) - val results: List = this.rules.filterNotNull() - .produceRuleResults({ onError(this, it) }, lockFile, configFile, this.name) + val results: List = this.rules.filterNotNull().produceRuleResults( + onError = { onError(this, it) }, + lockFile, configFile, this.name, overrides, serverOverrides, clientOverrides + ) // Run export rules val cachedPaths: List = results.resolveResults { onError(this, it) }.awaitAll().filterNotNull() + @@ -281,7 +299,10 @@ suspend fun List.finishResults( */ suspend fun List.produceRuleResults( onError: suspend (error: ActionError) -> Unit, - lockFile: LockFile, configFile: ConfigFile, workingSubDir: String + lockFile: LockFile, configFile: ConfigFile, workingSubDir: String, + overrides: Deferred>>, + serverOverrides: Deferred>>, + clientOverrides: Deferred>>, ): List = coroutineScope { val projects: Deferred> = async { lockFile.getAllProjects().map { @@ -289,8 +310,8 @@ suspend fun List.produceRuleResults( } } - val overrides: Deferred> = async { - configFile.getAllOverrides().await().mapNotNull { result -> + val overridesR: Deferred> = async { + overrides.await().mapNotNull { result -> result.resultFold( success = { RuleContext.ExportingOverride(it, OverrideType.OVERRIDE, lockFile, configFile, workingSubDir) @@ -303,8 +324,8 @@ suspend fun List.produceRuleResults( } } - val serverOverrides: Deferred> = async { - configFile.getAllServerOverrides().await().mapNotNull { result -> + val serverOverridesR: Deferred> = async { + serverOverrides.await().mapNotNull { result -> result.resultFold( success = { RuleContext.ExportingOverride(it, OverrideType.SERVER_OVERRIDE, lockFile, configFile, workingSubDir) @@ -317,8 +338,8 @@ suspend fun List.produceRuleResults( } } - val clientOverrides: Deferred> = async { - configFile.getAllClientOverrides().await().mapNotNull { result -> + val clientOverridesR: Deferred> = async { + clientOverrides.await().mapNotNull { result -> result.resultFold( success = { RuleContext.ExportingOverride(it, OverrideType.CLIENT_OVERRIDE, lockFile, configFile, workingSubDir) @@ -337,7 +358,7 @@ suspend fun List.produceRuleResults( } } - val deferredContexts = listOf(projects, overrides, serverOverrides, clientOverrides, projectOverrides) + val deferredContexts = listOf(projects, overridesR, serverOverridesR, clientOverridesR, projectOverrides) val contexts = deferredContexts.awaitAll().flatten() val results = this@produceRuleResults.fold(listOf>()) { acc, rule -> diff --git a/src/commonMain/kotlin/teksturepako/pakku/api/data/ConfigFile.kt b/src/commonMain/kotlin/teksturepako/pakku/api/data/ConfigFile.kt index 383a6ab7..daa14ef7 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/api/data/ConfigFile.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/api/data/ConfigFile.kt @@ -101,14 +101,14 @@ data class ConfigFile( this.overrides.clear() } - fun getAllOverrides(): List> = this.overrides - .map { filterPath(it) } + suspend fun getAllOverrides(): List> = + this.overrides.expandWithGlob(Path(workingPath)).map { filterPath(it) } - fun getAllServerOverrides(): List> = this.serverOverrides - .map { filterPath(it) } + suspend fun getAllServerOverrides(): List> = + this.serverOverrides.expandWithGlob(Path(workingPath)).map { filterPath(it) } - fun getAllClientOverrides(): List> = this.clientOverrides - .map { filterPath(it) } + suspend fun getAllClientOverrides(): List> = + this.clientOverrides.expandWithGlob(Path(workingPath)).map { filterPath(it) } // -- PROJECTS -- From 7312493077d98aa1dd45104484286ac9964263a1 Mon Sep 17 00:00:00 2001 From: Juraj Hrivnak Date: Mon, 16 Dec 2024 20:57:40 +0100 Subject: [PATCH 3/5] Create mini framework for tests & add tests for glob --- .../kotlin/teksturepako/pakku/io/Glob.kt | 9 ++- .../kotlin/teksturepako/pakku/PakkuTest.kt | 60 +++++++++++++++++++ .../pakku/api/actions/export/TestExport.kt | 6 -- .../pakku/api/platforms/CurseForgeTest.kt | 5 -- .../pakku/api/projects/ProjectTest.kt | 6 +- .../teksturepako/pakku/cli/cmd/CfgPrjTest.kt | 16 ++--- .../teksturepako/pakku/cli/cmd/CfgTest.kt | 14 +---- .../teksturepako/pakku/cli/cmd/ExportTest.kt | 11 +--- .../kotlin/teksturepako/pakku/io/GlobTest.kt | 40 +++++++++++++ 9 files changed, 120 insertions(+), 47 deletions(-) create mode 100644 src/commonTest/kotlin/teksturepako/pakku/PakkuTest.kt delete mode 100644 src/commonTest/kotlin/teksturepako/pakku/api/actions/export/TestExport.kt create mode 100644 src/commonTest/kotlin/teksturepako/pakku/io/GlobTest.kt diff --git a/src/commonMain/kotlin/teksturepako/pakku/io/Glob.kt b/src/commonMain/kotlin/teksturepako/pakku/io/Glob.kt index 8fdd4997..ff6ceb5b 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/io/Glob.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/io/Glob.kt @@ -13,8 +13,8 @@ import java.nio.file.PathMatcher import kotlin.io.path.* @OptIn(ExperimentalPathApi::class) -suspend fun Path.walk(inputGlobs: List): Sequence> = withContext(Dispatchers.IO) { - val matchers: List> = inputGlobs.mapNotNull { input -> +suspend fun Path.walk(globPatterns: List): Sequence> = withContext(Dispatchers.IO) { + val matchers: List> = globPatterns.mapNotNull { input -> val negating = input.startsWith("!") val glob = (if (negating) input.removePrefix("!") else input).removePrefix("./") @@ -30,7 +30,10 @@ suspend fun Path.walk(inputGlobs: List): Sequence> = matcher to path }.map { (matchers, path) -> - Path(path.absolutePathString().removePrefix(this@walk.absolutePathString()).removePrefix(File.separator)) to matchers.any { it.second } + val resultPath = Path(path.absolutePathString().removePrefix(this@walk.absolutePathString()).removePrefix(File.separator)) + val negating = matchers.any { it.second } + + resultPath to negating } } diff --git a/src/commonTest/kotlin/teksturepako/pakku/PakkuTest.kt b/src/commonTest/kotlin/teksturepako/pakku/PakkuTest.kt new file mode 100644 index 00000000..64d20854 --- /dev/null +++ b/src/commonTest/kotlin/teksturepako/pakku/PakkuTest.kt @@ -0,0 +1,60 @@ +package teksturepako.pakku + +import kotlinx.coroutines.runBlocking +import teksturepako.pakku.api.data.generatePakkuId +import teksturepako.pakku.api.data.workingPath +import kotlin.io.path.* +import kotlin.test.AfterTest +import kotlin.test.BeforeTest + +open class PakkuTest +{ + private var testName: String = "" + + protected open suspend fun `on-set-up`() + { + } + + protected open val teardown = true + + protected fun createTestFile(vararg path: String) + { + val file = Path(workingPath, *path) + runCatching { file.createParentDirectories() } + runCatching { file.createFile() } + } + + protected fun createTestDir(vararg path: String) + { + val dir = Path(workingPath, *path) + runCatching { dir.createParentDirectories() } + runCatching { dir.createDirectory() } + } + + @BeforeTest + fun `set-up-test`() + { + testName = this::class.simpleName ?: generatePakkuId() + + println("Setting up test: $testName") + + workingPath = "./build/test/$testName" + runCatching { Path("./build/test/$testName").createParentDirectories() } + runCatching { Path("./build/test/$testName").createDirectory() } + + runBlocking { this@PakkuTest.`on-set-up`() } + } + + @AfterTest + @OptIn(ExperimentalPathApi::class) + fun `tear-down-test`() + { + if (!teardown) return + + workingPath = "." + + println("Tearing down test: $testName") + + runCatching { Path("./build/test/$testName").deleteRecursively() } + } +} \ No newline at end of file diff --git a/src/commonTest/kotlin/teksturepako/pakku/api/actions/export/TestExport.kt b/src/commonTest/kotlin/teksturepako/pakku/api/actions/export/TestExport.kt deleted file mode 100644 index ad60638d..00000000 --- a/src/commonTest/kotlin/teksturepako/pakku/api/actions/export/TestExport.kt +++ /dev/null @@ -1,6 +0,0 @@ -package teksturepako.pakku.api.actions.export - -class TestExport -{ - -} \ No newline at end of file diff --git a/src/commonTest/kotlin/teksturepako/pakku/api/platforms/CurseForgeTest.kt b/src/commonTest/kotlin/teksturepako/pakku/api/platforms/CurseForgeTest.kt index 32a32275..9be86166 100644 --- a/src/commonTest/kotlin/teksturepako/pakku/api/platforms/CurseForgeTest.kt +++ b/src/commonTest/kotlin/teksturepako/pakku/api/platforms/CurseForgeTest.kt @@ -11,11 +11,6 @@ import kotlin.test.assertEquals class CurseForgeTest { - @Test - fun requestProject() - { - } - @Test fun sortByLoaders_WithValidLoaders_ShouldCompareCorrectly() { val files = listOf( diff --git a/src/commonTest/kotlin/teksturepako/pakku/api/projects/ProjectTest.kt b/src/commonTest/kotlin/teksturepako/pakku/api/projects/ProjectTest.kt index bb5723ff..d4d3db81 100644 --- a/src/commonTest/kotlin/teksturepako/pakku/api/projects/ProjectTest.kt +++ b/src/commonTest/kotlin/teksturepako/pakku/api/projects/ProjectTest.kt @@ -10,21 +10,21 @@ class ProjectTest @Test fun hasAliasOf_whenProjectHasAlias_returnsTrue() { - val project1 = mockk() { + val project1 = mockk { every { id } returns mutableMapOf("id1" to "id1") every { name } returns mutableMapOf("name1" to "name1") every { slug } returns mutableMapOf("slug1" to "slug1") every { aliases } returns mutableSetOf("id3") every { hasAliasOf(any()) } answers { callOriginal() } } - val project2 = mockk() { + val project2 = mockk { every { id } returns mutableMapOf("id2" to "id2") every { name } returns mutableMapOf("name2" to "name2") every { slug } returns mutableMapOf("slug2" to "slug2") every { aliases } returns mutableSetOf("name1") every { hasAliasOf(any()) } answers { callOriginal() } } - val project3 = mockk() { + val project3 = mockk { every { id } returns mutableMapOf("id3" to "id3") every { name } returns mutableMapOf("name3" to "name3") every { slug } returns mutableMapOf("slug3" to "slug3") diff --git a/src/commonTest/kotlin/teksturepako/pakku/cli/cmd/CfgPrjTest.kt b/src/commonTest/kotlin/teksturepako/pakku/cli/cmd/CfgPrjTest.kt index 5058d9a0..bcfc5546 100644 --- a/src/commonTest/kotlin/teksturepako/pakku/cli/cmd/CfgPrjTest.kt +++ b/src/commonTest/kotlin/teksturepako/pakku/cli/cmd/CfgPrjTest.kt @@ -3,6 +3,7 @@ package teksturepako.pakku.cli.cmd import com.github.ajalt.clikt.testing.test import com.github.michaelbull.result.runCatching import kotlinx.coroutines.runBlocking +import teksturepako.pakku.PakkuTest import teksturepako.pakku.api.data.ConfigFile import teksturepako.pakku.api.data.LockFile import teksturepako.pakku.api.data.workingPath @@ -16,21 +17,13 @@ import kotlin.test.assertContains import kotlin.test.assertEquals import kotlin.test.assertNotNull -@OptIn(ExperimentalPathApi::class) -class CfgPrjTest +class CfgPrjTest : PakkuTest() { - init - { - workingPath = "./build/test" - runCatching { Path("./build/test").deleteRecursively() } - runCatching { Path("./build/test").createDirectory() } - } - @Test fun `should fail without lock file`() { val cmd = CfgPrj() - val output = cmd.test("test -p test").output + val output = cmd.test("test --subpath test-subpath").output assertContains(output, "Could not read '$workingPath/${LockFile.FILE_NAME}'") } @@ -54,9 +47,12 @@ class CfgPrjTest val cmd = CfgPrj() val output = cmd.test("test -p test -s both -u latest -r true") + assertEquals("", output.stderr, "Command failed to execute") assertNotNull(ConfigFile.readOrNull(), "Config file should be created") + val config = ConfigFile.readOrNull()!!.projects["test"] + assertNotNull(config, "Project config should be created") assertEquals(UpdateStrategy.LATEST, config.updateStrategy) assertEquals(true, config.redistributable) diff --git a/src/commonTest/kotlin/teksturepako/pakku/cli/cmd/CfgTest.kt b/src/commonTest/kotlin/teksturepako/pakku/cli/cmd/CfgTest.kt index e602a432..9a0ad861 100644 --- a/src/commonTest/kotlin/teksturepako/pakku/cli/cmd/CfgTest.kt +++ b/src/commonTest/kotlin/teksturepako/pakku/cli/cmd/CfgTest.kt @@ -2,6 +2,7 @@ package teksturepako.pakku.cli.cmd import com.github.ajalt.clikt.testing.test import com.github.michaelbull.result.runCatching +import teksturepako.pakku.PakkuTest import teksturepako.pakku.api.data.ConfigFile import teksturepako.pakku.api.data.workingPath import teksturepako.pakku.api.projects.ProjectType @@ -10,22 +11,13 @@ import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull -@OptIn(ExperimentalPathApi::class) -class CfgTest +class CfgTest : PakkuTest() { - init - { - workingPath = "./build/test" - runCatching { Path("./build/test").deleteRecursively() } - runCatching { Path("./build/test").createDirectory() } - } - @Test fun `should success with options`() { val cmd = Cfg() - val output = - cmd.test("-n foo -v 1.20.1 -d bar -a test --mods-path ./dummy-mods --resource-packs-path ./dummy-resourcepacks --data-packs-path ./datapacks --worlds-path ./worlds --shaders-path ./shaders") + val output = cmd.test("-n foo -v 1.20.1 -d bar -a test --mods-path ./dummy-mods --resource-packs-path ./dummy-resourcepacks --data-packs-path ./datapacks --worlds-path ./worlds --shaders-path ./shaders") assertEquals("", output.stderr, "Command failed to execute") val config = ConfigFile.readOrNull() assertNotNull(config, "Config file should be created") diff --git a/src/commonTest/kotlin/teksturepako/pakku/cli/cmd/ExportTest.kt b/src/commonTest/kotlin/teksturepako/pakku/cli/cmd/ExportTest.kt index 7c5d59e8..20cbd077 100644 --- a/src/commonTest/kotlin/teksturepako/pakku/cli/cmd/ExportTest.kt +++ b/src/commonTest/kotlin/teksturepako/pakku/cli/cmd/ExportTest.kt @@ -2,27 +2,20 @@ package teksturepako.pakku.cli.cmd import com.github.ajalt.clikt.testing.test import com.github.michaelbull.result.runCatching +import teksturepako.pakku.PakkuTest import teksturepako.pakku.api.data.LockFile import teksturepako.pakku.api.data.workingPath import kotlin.io.path.* import kotlin.test.Test import kotlin.test.assertContains -@OptIn(ExperimentalPathApi::class) -class ExportTest +class ExportTest : PakkuTest() { private val testName = "TestModpack" private val testVersion = "1.0.0" private val testDescription = "This is a test modpack." private val testAuthor = "TestAuthor" - init - { - workingPath = "./build/test" - runCatching { Path("./build/test").deleteRecursively() } - runCatching { Path("./build/test").createDirectory() } - } - @Test fun `try without lock file & config file`() { diff --git a/src/commonTest/kotlin/teksturepako/pakku/io/GlobTest.kt b/src/commonTest/kotlin/teksturepako/pakku/io/GlobTest.kt new file mode 100644 index 00000000..3cdd745f --- /dev/null +++ b/src/commonTest/kotlin/teksturepako/pakku/io/GlobTest.kt @@ -0,0 +1,40 @@ +package teksturepako.pakku.io + +import kotlinx.coroutines.runBlocking +import teksturepako.pakku.PakkuTest +import teksturepako.pakku.api.data.workingPath +import kotlin.io.path.Path +import kotlin.test.Test +import kotlin.test.assertContains + +class GlobTest : PakkuTest() +{ + private val testFileName = "test_file.txt" + private val testDirName = "test_dir" + + override suspend fun `on-set-up`() + { + createTestFile(testFileName) + createTestDir(testDirName) + createTestFile(testDirName, testFileName) + } + + @Test + fun `test with basic file name`() = runBlocking { + val expandedGlob = listOf(testFileName).expandWithGlob(Path(workingPath)) + assertContains(expandedGlob, testFileName) + } + + @Test + fun `test with negating pattern`() = runBlocking { + val expandedGlob = listOf("!$testFileName").expandWithGlob(Path(workingPath)) + assert(testFileName !in expandedGlob) + } + + @Test + fun `test with dir pattern`() = runBlocking { + val expandedGlob = listOf("$testDirName/**").expandWithGlob(Path(workingPath)) + assert(testDirName !in expandedGlob) { "$expandedGlob should not contain $testDirName" } + assertContains(expandedGlob, "$testDirName/$testFileName") + } +} \ No newline at end of file From aad017641652ccdfa9b22ea5cc7d67f0aa66a048 Mon Sep 17 00:00:00 2001 From: Juraj Hrivnak Date: Mon, 16 Dec 2024 23:11:26 +0100 Subject: [PATCH 4/5] Fix export message file path string --- src/commonMain/kotlin/teksturepako/pakku/cli/cmd/Export.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/commonMain/kotlin/teksturepako/pakku/cli/cmd/Export.kt b/src/commonMain/kotlin/teksturepako/pakku/cli/cmd/Export.kt index 8a6452bc..5e7c9e41 100644 --- a/src/commonMain/kotlin/teksturepako/pakku/cli/cmd/Export.kt +++ b/src/commonMain/kotlin/teksturepako/pakku/cli/cmd/Export.kt @@ -19,8 +19,6 @@ import teksturepako.pakku.cli.ui.pError import teksturepako.pakku.cli.ui.pSuccess import teksturepako.pakku.cli.ui.shortForm import teksturepako.pakku.io.toHumanReadableSize -import teksturepako.pakku.io.tryOrNull -import kotlin.io.path.absolutePathString import kotlin.io.path.fileSize class Export : CliktCommand() @@ -60,9 +58,8 @@ class Export : CliktCommand() }, onSuccess = { profile, file, duration -> val fileSize = file.fileSize().toHumanReadableSize() - val filePath = file.tryOrNull { it.absolutePathString() } ?: file.toString() - terminal.pSuccess("[${profile.name} profile] exported to '$filePath' ($fileSize) in ${duration.shortForm()}") + terminal.pSuccess("[${profile.name} profile] exported to '$file' ($fileSize) in ${duration.shortForm()}") }, lockFile, configFile, platforms ).joinAll() From c5b6c3d11b092fe8864b154c3bfcb6d9be864c5a Mon Sep 17 00:00:00 2001 From: Juraj Hrivnak Date: Mon, 16 Dec 2024 23:25:02 +0100 Subject: [PATCH 5/5] Update CHANGELOG.md & bump version --- CHANGELOG.md | 19 +++++++++++++++++++ build.gradle.kts | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d5035cc..8ef97daf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,26 @@ + # Changelog ## Unreleased +## v0.22.0 + +### Highlights + +- When removing a project, Pakku will always ask whether you want to remove each of its dependencies. +- Added glob pattern support for specifying overrides, server_overrides and client_overrides, in [#70](https://github.com/juraj-hrivnak/Pakku/pull/70). + - This new implementation is optimized to work with large modpacks. + - For more info see [v0.20.0](https://github.com/juraj-hrivnak/Pakku/releases/tag/v0.20.0). + +### Fixes + +- Removed hyperlink from exported modpack file path messages. They didn't work properly on all platforms. + +### API + +- Breaking change: Refactored action errors to separate data classes. +- Created a mini framework for simplifying tests. + ## v0.21.0 - Improved projects updating, by @SettingDust in [#49](https://github.com/juraj-hrivnak/Pakku/pull/49). diff --git a/build.gradle.kts b/build.gradle.kts index c126a61e..eef34339 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -16,7 +16,7 @@ plugins { } group = "teksturepako.pakku" -version = "0.21.0" +version = "0.22.0" val nativeEnabled = false