From f04f1d18c4621c037129f732cfe804bafeb34cdd Mon Sep 17 00:00:00 2001 From: "Alexander.Likhachev" Date: Wed, 3 May 2023 20:28:41 +0200 Subject: [PATCH] [Build, IGS] Implement ConsentManager #KTI-1223 In Progress --- .../src/main/kotlin/ConsentManager.kt | 59 ++++++++++++++ .../main/kotlin/LocalPropertiesModifier.kt | 17 +++-- .../src/test/kotlin/ConsentManagerTest.kt | 76 +++++++++++++++++++ .../kotlin/LocalPropertiesModifierTest.kt | 17 +---- .../src/test/kotlin/utils.kt | 25 ++++++ 5 files changed, 174 insertions(+), 20 deletions(-) create mode 100644 repo/gradle-settings-conventions/internal-gradle-setup/src/main/kotlin/ConsentManager.kt create mode 100644 repo/gradle-settings-conventions/internal-gradle-setup/src/test/kotlin/ConsentManagerTest.kt create mode 100644 repo/gradle-settings-conventions/internal-gradle-setup/src/test/kotlin/utils.kt diff --git a/repo/gradle-settings-conventions/internal-gradle-setup/src/main/kotlin/ConsentManager.kt b/repo/gradle-settings-conventions/internal-gradle-setup/src/main/kotlin/ConsentManager.kt new file mode 100644 index 00000000000..55ac172f7bc --- /dev/null +++ b/repo/gradle-settings-conventions/internal-gradle-setup/src/main/kotlin/ConsentManager.kt @@ -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 + } + } + } + } +} \ No newline at end of file diff --git a/repo/gradle-settings-conventions/internal-gradle-setup/src/main/kotlin/LocalPropertiesModifier.kt b/repo/gradle-settings-conventions/internal-gradle-setup/src/main/kotlin/LocalPropertiesModifier.kt index 2f092cf8e88..e996152a40f 100644 --- a/repo/gradle-settings-conventions/internal-gradle-setup/src/main/kotlin/LocalPropertiesModifier.kt +++ b/repo/gradle-settings-conventions/internal-gradle-setup/src/main/kotlin/LocalPropertiesModifier.kt @@ -20,11 +20,13 @@ internal val SYNCED_PROPERTIES_START_LINES = """ internal const val SYNCED_PROPERTIES_END_LINE = "# the end of automatically configured properties" internal class LocalPropertiesModifier(private val localProperties: File) { - private val manuallyConfiguredPropertiesContent by lazy { - if (!localProperties.exists()) return@lazy "" + private val initialUserConfiguredPropertiesContent = getUserConfiguredPropertiesContent() + + private fun getUserConfiguredPropertiesContent(): String { + if (!localProperties.exists()) return "" var insideAutomaticallyConfiguredSection = false // filter out the automatically configured lines - localProperties.readLines().filter { line -> + return localProperties.readLines().filter { line -> if (line == SYNCED_PROPERTIES_START_LINE) { insideAutomaticallyConfiguredSection = true } @@ -45,15 +47,16 @@ internal class LocalPropertiesModifier(private val localProperties: File) { if (localProperties.exists() && !localProperties.isFile) { error("$localProperties is not a file!") } + val content = getUserConfiguredPropertiesContent() val manuallyConfiguredProperties = Properties().apply { - StringReader(manuallyConfiguredPropertiesContent).use { + StringReader(content).use { load(it) } } val propertiesToSetup = setupFile.properties.mapValues { PropertyValue(it.value, manuallyConfiguredProperties.containsKey(it.key)) } localProperties.writeText( """ - |${manuallyConfiguredPropertiesContent.addSuffix("\n")} + |${content.addSuffix("\n")} |$SYNCED_PROPERTIES_START_LINES |${propertiesToSetup.asPropertiesLines} |$SYNCED_PROPERTIES_END_LINE @@ -61,6 +64,10 @@ internal class LocalPropertiesModifier(private val localProperties: File) { """.trimMargin() ) } + + fun initiallyContains(line: String) = initialUserConfiguredPropertiesContent.contains(line) + + fun putLine(line: String) = localProperties.appendText("\n$line") } private fun String.addSuffix(suffix: String): String { diff --git a/repo/gradle-settings-conventions/internal-gradle-setup/src/test/kotlin/ConsentManagerTest.kt b/repo/gradle-settings-conventions/internal-gradle-setup/src/test/kotlin/ConsentManagerTest.kt new file mode 100644 index 00000000000..ddd5c449266 --- /dev/null +++ b/repo/gradle-settings-conventions/internal-gradle-setup/src/test/kotlin/ConsentManagerTest.kt @@ -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) + } + } + } +} \ No newline at end of file diff --git a/repo/gradle-settings-conventions/internal-gradle-setup/src/test/kotlin/LocalPropertiesModifierTest.kt b/repo/gradle-settings-conventions/internal-gradle-setup/src/test/kotlin/LocalPropertiesModifierTest.kt index ec9bf669d5f..49ede34dbd3 100644 --- a/repo/gradle-settings-conventions/internal-gradle-setup/src/test/kotlin/LocalPropertiesModifierTest.kt +++ b/repo/gradle-settings-conventions/internal-gradle-setup/src/test/kotlin/LocalPropertiesModifierTest.kt @@ -205,21 +205,8 @@ class LocalPropertiesModifierTest { } private fun assertContainsMarkersOnce(content: String) { - assertContainsOnce(content, SYNCED_PROPERTIES_START_LINES) - assertContainsOnce(content, SYNCED_PROPERTIES_END_LINE) - } - - 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) + assertContainsExactTimes(content, SYNCED_PROPERTIES_START_LINES, 1) + assertContainsExactTimes(content, SYNCED_PROPERTIES_END_LINE, 1) } private fun fillInitialLocalPropertiesFile(content: Map) { diff --git a/repo/gradle-settings-conventions/internal-gradle-setup/src/test/kotlin/utils.kt b/repo/gradle-settings-conventions/internal-gradle-setup/src/test/kotlin/utils.kt new file mode 100644 index 00000000000..4ed8121cf6d --- /dev/null +++ b/repo/gradle-settings-conventions/internal-gradle-setup/src/test/kotlin/utils.kt @@ -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() + } +} \ No newline at end of file