Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions buildSrc/src/main/kotlin/config-kotlin.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ testing {
useKotlinTest(embeddedKotlinVersion)
dependencies {
implementation("org.junit.jupiter:junit-jupiter-engine:5.10.1")
implementation("org.junit.jupiter:junit-jupiter-params:5.10.1")
implementation("org.junit.platform:junit-platform-launcher:1.10.1")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,19 @@ import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.options.Option

/**
* Patch roulette apply allows selection a set of patches from the remote patch roulette instance to work on.
* To control the amount/strategy of selecting these patches, the `--select` option can be passed.
* The following options are available:
* - `n`: Any positive integer number.
* Paperweight will select *up to* `n` patches from the current package the user is working in.
* If the package offers 0 patches, a new package will be chosen.
* If the package offers `m` patches, and `m < n`, only `m` patches will be returned.
* - `n!`: Any positive integer number followed by a `!`.
* Paperweight will select `n` patches, prioritizing patches in the current package.
* The only time less than `n` patches will be selected is if the entire patch roulette
* instance has less than `n` patches available, in which case all of them will be selected.
*/
abstract class PatchRouletteApply : AbstractPatchRouletteTask() {

@get:InputDirectory
Expand Down Expand Up @@ -87,7 +100,7 @@ abstract class PatchRouletteApply : AbstractPatchRouletteTask() {
}

var tries = 5
var patches = listOf<Path>()
var patches: List<Path>
val patchSelectionStrategy = patchSelectionStrategy
.map { PatchSelectionStrategy.parse(it) }
.getOrElse(PatchSelectionStrategy.NumericInPackage(5))
Expand Down Expand Up @@ -166,14 +179,33 @@ abstract class PatchRouletteApply : AbstractPatchRouletteTask() {
data class Config(val skip: List<Path>, val suggestedPackage: Path?, val currentPatches: List<Path>)

sealed interface PatchSelectionStrategy {
data class NumericInPackage(val count: Int) : PatchSelectionStrategy {
data class NumericInPackage(val count: Int, val enforceCount: Boolean = false) : PatchSelectionStrategy {
override fun select(config: Config, available: List<Path>): Pair<Config, List<Path>> {
return this.select(config, available, this.count)
}

fun select(config: Config, available: List<Path>, count: Int): Pair<Config, List<Path>> {
if (config.suggestedPackage != null) {
val possiblePatches = available.filter { it.parent.equals(config.suggestedPackage) }.take(count)
if (possiblePatches.isNotEmpty()) return config to possiblePatches
if (possiblePatches.isNotEmpty()) {
if (!enforceCount) return config to possiblePatches
// The patches we found satisfy the count param or the entire available set simply does not offer enough patches.
if (possiblePatches.size >= count || possiblePatches.size == available.size) return config to possiblePatches

// The patches found in the package do not satisfy the requested count *and* the strategy was configured to enforce the
// count. Re-select from a new package and different patch set, add them to our already fetched patches and update the
// config, as the last suggested package is the one to suggest in potentially next runs.
val additionalPatches = select(
config.copy(suggestedPackage = null),
available.filter { !possiblePatches.contains(it) },
count - possiblePatches.size
)

return additionalPatches.first to possiblePatches + additionalPatches.second
}
}

return select(config.copy(suggestedPackage = available.first().parent), available)
return select(config.copy(suggestedPackage = available.first().parent), available, count)
}
}

Expand All @@ -182,7 +214,10 @@ abstract class PatchRouletteApply : AbstractPatchRouletteTask() {
companion object {
fun parse(input: String): PatchSelectionStrategy {
try {
return NumericInPackage(input.toInt())
return when {
input.endsWith("!") -> NumericInPackage(input.substring(0, input.length - 1).toInt(), true)
else -> NumericInPackage(input.toInt())
}
} catch (e: Exception) {
throw PaperweightException("Failed to parse patch selection strategy $input", e)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
* paperweight is a Gradle plugin for the PaperMC project.
*
* Copyright (c) 2023 Kyle Wood (DenWav)
* Contributors
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 only, no later versions.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
* USA
*/

package io.papermc.paperweight.core.tasks.patchroulette

import kotlin.io.path.*
import kotlin.test.Test
import kotlin.test.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource

class PatchRouletteApplyTest {

@Test
fun `test patch strategy parsing non enforced`() {
val strategy = PatchRouletteApply.PatchSelectionStrategy.parse("10")
when (strategy) {
is PatchRouletteApply.PatchSelectionStrategy.NumericInPackage -> {
assertEquals(10, strategy.count)
assertFalse(strategy.enforceCount)
}
}
}

@Test
fun `test patch strategy parsing enforced`() {
val strategy = PatchRouletteApply.PatchSelectionStrategy.parse("20!")
when (strategy) {
is PatchRouletteApply.PatchSelectionStrategy.NumericInPackage -> {
assertEquals(20, strategy.count)
assertTrue(strategy.enforceCount)
}
}
}

@ParameterizedTest
@MethodSource("testPatchSelectionSource")
fun `test patch selection`(
strategy: PatchRouletteApply.PatchSelectionStrategy,
allPatches: List<String>,
expectedPatchBatches: List<List<String>>
) {
var config = PatchRouletteApply.Config(listOf(), null, listOf())
var availablePatches = allPatches.map { Path(it) }
for (batch in expectedPatchBatches) {
val selectionResult = strategy.select(config, availablePatches)
assertEquals(batch.map { Path(it) }, selectionResult.second)

config = selectionResult.first
availablePatches = availablePatches - selectionResult.second
}

assertTrue(availablePatches.isEmpty(), "Patches remained after exhausting expected batches")
}

companion object {
@JvmStatic
fun testPatchSelectionSource(): Collection<Arguments> = listOf(
Arguments.of(
PatchRouletteApply.PatchSelectionStrategy.NumericInPackage(2),
mockAvailablePatches(),
listOf(
listOf("io/papermc/paper/block/Block.java", "io/papermc/paper/block/BlockData.java"),
listOf("io/papermc/paper/block/BlockState.java"),
listOf("io/papermc/paper/entity/Entity.java")
)
),
Arguments.of(
PatchRouletteApply.PatchSelectionStrategy.NumericInPackage(5),
mockAvailablePatches(),
listOf(
listOf("io/papermc/paper/block/Block.java", "io/papermc/paper/block/BlockData.java", "io/papermc/paper/block/BlockState.java"),
listOf("io/papermc/paper/entity/Entity.java")
)
),
Arguments.of(
PatchRouletteApply.PatchSelectionStrategy.NumericInPackage(2, true),
mockAvailablePatches(),
listOf(
listOf("io/papermc/paper/block/Block.java", "io/papermc/paper/block/BlockData.java"),
listOf("io/papermc/paper/block/BlockState.java", "io/papermc/paper/entity/Entity.java"),
)
),
Arguments.of(
PatchRouletteApply.PatchSelectionStrategy.NumericInPackage(5, true),
mockAvailablePatches(),
listOf(
listOf(
"io/papermc/paper/block/Block.java",
"io/papermc/paper/block/BlockData.java",
"io/papermc/paper/block/BlockState.java",
"io/papermc/paper/entity/Entity.java"
),
)
),
Arguments.of(
PatchRouletteApply.PatchSelectionStrategy.NumericInPackage(4, true),
listOf(
"io/papermc/paper/block/Block.java",
"io/papermc/paper/block/BlockData.java",
"io/papermc/paper/block/BlockState.java",
"io/papermc/paper/entity/Entity.java",
"io/papermc/paper/entity/Entity2.java",
"io/papermc/paper/entity/Entity3.java"
),
listOf(
listOf(
"io/papermc/paper/block/Block.java",
"io/papermc/paper/block/BlockData.java",
"io/papermc/paper/block/BlockState.java",
"io/papermc/paper/entity/Entity.java",
),
listOf(
"io/papermc/paper/entity/Entity2.java",
"io/papermc/paper/entity/Entity3.java"
)
)
)
)

fun mockAvailablePatches() = listOf(
"io/papermc/paper/block/Block.java",
"io/papermc/paper/block/BlockData.java",
"io/papermc/paper/block/BlockState.java",
"io/papermc/paper/entity/Entity.java"
)
}
}
Loading