[Build, IGS] Implement ConsentManager
#KTI-1223 In Progress
This commit is contained in:
committed by
Space Team
parent
6804d0a0af
commit
f04f1d18c4
+59
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.build
|
||||||
|
|
||||||
|
import java.io.BufferedReader
|
||||||
|
import java.io.PrintStream
|
||||||
|
|
||||||
|
private val isInIdeaSync
|
||||||
|
get() = System.getProperty("idea.sync.active").toBoolean()
|
||||||
|
|
||||||
|
internal const val USER_CONSENT_MARKER = "# This line indicates that you have chosen to enable automatic configuration of properties."
|
||||||
|
|
||||||
|
internal const val USER_REFUSAL_MARKER =
|
||||||
|
"# This line indicates that you have chosen to disable automatic configuration of properties. If you want to enable it, remove this line."
|
||||||
|
|
||||||
|
internal val USER_CONSENT_REQUEST = """
|
||||||
|
|
||||||
|
! ATTENTION REQUIRED !
|
||||||
|
Most probably you're a developer from the Kotlin team. We are asking for your consent for automatic configuration of local.properties file
|
||||||
|
for providing some optimizations and collecting additional debug information.
|
||||||
|
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
internal const val PROMPT_REQUEST = "Do you agree with this? Please answer with 'yes' or 'no': "
|
||||||
|
|
||||||
|
internal class ConsentManager(
|
||||||
|
private val modifier: LocalPropertiesModifier,
|
||||||
|
private val input: BufferedReader = System.`in`.bufferedReader(),
|
||||||
|
private val output: PrintStream = System.out,
|
||||||
|
) {
|
||||||
|
fun getUserDecision() = when {
|
||||||
|
modifier.initiallyContains(USER_REFUSAL_MARKER) -> false
|
||||||
|
modifier.initiallyContains(USER_CONSENT_MARKER) -> true
|
||||||
|
isInIdeaSync -> false
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun askForConsent(): Boolean {
|
||||||
|
output.println(USER_CONSENT_REQUEST)
|
||||||
|
while (true) {
|
||||||
|
output.println(PROMPT_REQUEST)
|
||||||
|
when (input.readLine()) {
|
||||||
|
"yes" -> {
|
||||||
|
output.println("You've given the consent")
|
||||||
|
modifier.putLine(USER_CONSENT_MARKER)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
"no" -> {
|
||||||
|
output.println("You've refused to give the consent")
|
||||||
|
modifier.putLine(USER_REFUSAL_MARKER)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
-5
@@ -20,11 +20,13 @@ internal val SYNCED_PROPERTIES_START_LINES = """
|
|||||||
internal const val SYNCED_PROPERTIES_END_LINE = "# the end of automatically configured properties"
|
internal const val SYNCED_PROPERTIES_END_LINE = "# the end of automatically configured properties"
|
||||||
|
|
||||||
internal class LocalPropertiesModifier(private val localProperties: File) {
|
internal class LocalPropertiesModifier(private val localProperties: File) {
|
||||||
private val manuallyConfiguredPropertiesContent by lazy {
|
private val initialUserConfiguredPropertiesContent = getUserConfiguredPropertiesContent()
|
||||||
if (!localProperties.exists()) return@lazy ""
|
|
||||||
|
private fun getUserConfiguredPropertiesContent(): String {
|
||||||
|
if (!localProperties.exists()) return ""
|
||||||
var insideAutomaticallyConfiguredSection = false
|
var insideAutomaticallyConfiguredSection = false
|
||||||
// filter out the automatically configured lines
|
// filter out the automatically configured lines
|
||||||
localProperties.readLines().filter { line ->
|
return localProperties.readLines().filter { line ->
|
||||||
if (line == SYNCED_PROPERTIES_START_LINE) {
|
if (line == SYNCED_PROPERTIES_START_LINE) {
|
||||||
insideAutomaticallyConfiguredSection = true
|
insideAutomaticallyConfiguredSection = true
|
||||||
}
|
}
|
||||||
@@ -45,15 +47,16 @@ internal class LocalPropertiesModifier(private val localProperties: File) {
|
|||||||
if (localProperties.exists() && !localProperties.isFile) {
|
if (localProperties.exists() && !localProperties.isFile) {
|
||||||
error("$localProperties is not a file!")
|
error("$localProperties is not a file!")
|
||||||
}
|
}
|
||||||
|
val content = getUserConfiguredPropertiesContent()
|
||||||
val manuallyConfiguredProperties = Properties().apply {
|
val manuallyConfiguredProperties = Properties().apply {
|
||||||
StringReader(manuallyConfiguredPropertiesContent).use {
|
StringReader(content).use {
|
||||||
load(it)
|
load(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val propertiesToSetup = setupFile.properties.mapValues { PropertyValue(it.value, manuallyConfiguredProperties.containsKey(it.key)) }
|
val propertiesToSetup = setupFile.properties.mapValues { PropertyValue(it.value, manuallyConfiguredProperties.containsKey(it.key)) }
|
||||||
localProperties.writeText(
|
localProperties.writeText(
|
||||||
"""
|
"""
|
||||||
|${manuallyConfiguredPropertiesContent.addSuffix("\n")}
|
|${content.addSuffix("\n")}
|
||||||
|$SYNCED_PROPERTIES_START_LINES
|
|$SYNCED_PROPERTIES_START_LINES
|
||||||
|${propertiesToSetup.asPropertiesLines}
|
|${propertiesToSetup.asPropertiesLines}
|
||||||
|$SYNCED_PROPERTIES_END_LINE
|
|$SYNCED_PROPERTIES_END_LINE
|
||||||
@@ -61,6 +64,10 @@ internal class LocalPropertiesModifier(private val localProperties: File) {
|
|||||||
""".trimMargin()
|
""".trimMargin()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun initiallyContains(line: String) = initialUserConfiguredPropertiesContent.contains(line)
|
||||||
|
|
||||||
|
fun putLine(line: String) = localProperties.appendText("\n$line")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun String.addSuffix(suffix: String): String {
|
private fun String.addSuffix(suffix: String): String {
|
||||||
|
|||||||
+76
@@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.build
|
||||||
|
|
||||||
|
import org.apache.tools.ant.filters.StringInputStream
|
||||||
|
import org.junit.jupiter.api.DisplayName
|
||||||
|
import org.junit.jupiter.api.io.TempDir
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.PrintStream
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import kotlin.test.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertNull
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
|
class ConsentManagerTest {
|
||||||
|
@TempDir
|
||||||
|
lateinit var workingDir: Path
|
||||||
|
|
||||||
|
private val localPropertiesFile by lazy {
|
||||||
|
workingDir.resolve("local.properties")
|
||||||
|
}
|
||||||
|
|
||||||
|
private val modifier by lazy {
|
||||||
|
LocalPropertiesModifier(localPropertiesFile.toFile())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("can check if there's no decision over the consent")
|
||||||
|
fun testNoConsentRead() {
|
||||||
|
assertNull(ConsentManager(modifier).getUserDecision())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("can check if there's agree to the consent")
|
||||||
|
fun testConsentAgreeRead() {
|
||||||
|
localPropertiesFile.toFile().writeText(USER_CONSENT_MARKER)
|
||||||
|
assertEquals(true, ConsentManager(modifier).getUserDecision())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@DisplayName("can check if there's refusal to the consent")
|
||||||
|
fun testConsentRefusalRead() {
|
||||||
|
localPropertiesFile.toFile().writeText(USER_REFUSAL_MARKER)
|
||||||
|
assertEquals(false, ConsentManager(modifier).getUserDecision())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testConsentRequestAgree() = testUserDecision("yes", true, USER_CONSENT_MARKER)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testConsentRequestRefusal() = testUserDecision("no", false, USER_REFUSAL_MARKER)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testConsentRequestAnswerAfterBadInput() = testUserDecision("\nasdasd\nno", false, USER_REFUSAL_MARKER, 3)
|
||||||
|
|
||||||
|
private fun testUserDecision(prompt: String, expectedDecision: Boolean, expectedLine: String, promptCount: Int = 1) {
|
||||||
|
StringInputStream(prompt).bufferedReader().use { input ->
|
||||||
|
val outputStream = ByteArrayOutputStream(255)
|
||||||
|
PrintStream(outputStream).use { printStream ->
|
||||||
|
val consentManager = ConsentManager(modifier, input, printStream)
|
||||||
|
val userDecision = consentManager.askForConsent()
|
||||||
|
assertEquals(expectedDecision, userDecision)
|
||||||
|
val content = Files.readAllLines(localPropertiesFile)
|
||||||
|
assertTrue(content.contains(expectedLine))
|
||||||
|
val output = String(outputStream.toByteArray())
|
||||||
|
assertContainsExactTimes(output, USER_CONSENT_REQUEST, 1)
|
||||||
|
assertContainsExactTimes(output, PROMPT_REQUEST, promptCount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-15
@@ -205,21 +205,8 @@ class LocalPropertiesModifierTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun assertContainsMarkersOnce(content: String) {
|
private fun assertContainsMarkersOnce(content: String) {
|
||||||
assertContainsOnce(content, SYNCED_PROPERTIES_START_LINES)
|
assertContainsExactTimes(content, SYNCED_PROPERTIES_START_LINES, 1)
|
||||||
assertContainsOnce(content, SYNCED_PROPERTIES_END_LINE)
|
assertContainsExactTimes(content, SYNCED_PROPERTIES_END_LINE, 1)
|
||||||
}
|
|
||||||
|
|
||||||
private fun assertContainsOnce(content: String, substring: String) {
|
|
||||||
var currentOffset = 0
|
|
||||||
var count = 0
|
|
||||||
var nextIndex = content.indexOf(substring, currentOffset)
|
|
||||||
|
|
||||||
while (nextIndex != -1 && count < 2) {
|
|
||||||
count++
|
|
||||||
currentOffset = nextIndex + substring.length
|
|
||||||
nextIndex = content.indexOf(substring, currentOffset)
|
|
||||||
}
|
|
||||||
assertEquals(1, count)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fillInitialLocalPropertiesFile(content: Map<String, PropertyValue>) {
|
private fun fillInitialLocalPropertiesFile(content: Map<String, PropertyValue>) {
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.build
|
||||||
|
|
||||||
|
internal fun assertContainsExactTimes(content: String, substring: String, expectedCount: Int) {
|
||||||
|
var currentOffset = 0
|
||||||
|
var count = 0
|
||||||
|
var nextIndex = content.indexOf(substring, currentOffset)
|
||||||
|
|
||||||
|
while (nextIndex != -1 && count < expectedCount + 1) {
|
||||||
|
count++
|
||||||
|
currentOffset = nextIndex + substring.length
|
||||||
|
nextIndex = content.indexOf(substring, currentOffset)
|
||||||
|
}
|
||||||
|
assert(expectedCount == count) {
|
||||||
|
"""
|
||||||
|
|The content is expected to contain '$substring' exactly $expectedCount times, but was found $count times:
|
||||||
|
|File content:
|
||||||
|
|${content.prependIndent()}
|
||||||
|
""".trimMargin()
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user