Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added save-overrides.toml #438

Merged
merged 19 commits into from
Aug 29, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 0 additions & 2 deletions OptionsTable.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,5 @@ Most (except for `-h` and `-prop`) of the options below can be passed to a SAVE
| l | language | Language that you are developing analyzer for | UNDEFINED |
| out | result-output | Data output stream | STDOUT |
| - | report-dir | Path to directory, where to store output (when `resultOutput` is set to `FILE`) | save-reports |
| - | override-exec-cmd | A temporary workaround for save-cloud to override `execCmd` in `save.toml` | - |
| - | override-exec-flags | A temporary workaround for save-cloud to override `execFlags` in `save.toml` | - |
| - | batch-size | Number of files execCmd will process at a time | 1 |
| - | batch-separator | A separator to join test files to string if the tested tool supports processing of file batches (`batch-size` > 1) | , |
16 changes: 0 additions & 16 deletions buildSrc/src/main/resources/config-options.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,22 +79,6 @@
"description" : "Path to directory, where to store output (when `resultOutput` is set to `FILE`)",
"default" : "save-reports"
},
"overrideExecCmd" : {
"argType": "ArgType.String",
"kotlinType": "kotlin.String",
"fullName": "override-exec-cmd",
"shortName" : "",
"description" : "A temporary workaround for save-cloud to override `execCmd` in `save.toml`",
"default" : null
},
"overrideExecFlags" : {
"argType": "ArgType.String",
"kotlinType": "kotlin.String",
"fullName": "override-exec-flags",
"shortName" : "",
"description" : "A temporary workaround for save-cloud to override `execFlags` in `save.toml`",
"default" : null
},
"batchSize" : {
"argType" : "ArgType.Int",
"kotlinType": "kotlin.Int",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,6 @@ private fun SaveProperties.validate(): SaveProperties {
ExitCodes.INVALID_CONFIGURATION, "Property `batch-size` should be more or equal to 1."
)
}
overrideExecCmd?.also {
logWarn {
"Property `override-exec-cmd` is a temporary workaround for `save-cloud`, " +
"please be aware this property can be removed in future versions"
}
}
overrideExecFlags?.also {
logWarn {
"Property `override-exec-flags` is a temporary workaround for `save-cloud`, " +
"please be aware this property can be removed in future versions"
}
}

return this
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,18 @@ internal expect val fs: FileSystem
* No-op implementation of [Plugin] that can be used to test reporters, which expect only a class name of the plugin.
*/
class MockPlugin(baseDir: Path, testFiles: List<String> = emptyList()) : Plugin(
TestConfig((baseDir / "save.toml").also { fs.createFile(it) }, null, fs = fs),
TestConfig(
(baseDir / "save.toml").also { fs.createFile(it) },
null,
EvaluatedToolConfig(1, ""),
overridesPluginConfigs = emptyList(),
fs = fs),
testFiles,
fs,
useInternalRedirections = true,
redirectTo = null
) {
override fun handleFiles(evaluatedToolConfig: EvaluatedToolConfig, files: Sequence<TestFiles>): Sequence<TestResult> = emptySequence()
override fun handleFiles(files: Sequence<TestFiles>): Sequence<TestResult> = emptySequence()

override fun rawDiscoverTestFiles(resourceDirectories: Sequence<Path>): Sequence<TestFiles> = emptySequence()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
package com.saveourtool.save.core.config

/**
* Configuration for an evaluated tool, that is read from test suite configuration file (toml config) or cli
* Configuration for an evaluated tool, that is read from cli
*
* @property execCmd
* @property execFlags
* @property batchSize it controls how many files execCmd will process at a time
* @property batchSeparator A separator to join test files to string if the tested tool supports processing of file batches (`batch-size` > 1)
*/
data class EvaluatedToolConfig(
val execCmd: String?,
val execFlags: String?,
nulls marked this conversation as resolved.
Show resolved Hide resolved
val batchSize: Int,
val batchSeparator: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import com.saveourtool.save.core.logging.logTrace
import com.saveourtool.save.core.plugin.GeneralConfig
import com.saveourtool.save.core.plugin.Plugin
import com.saveourtool.save.core.plugin.PluginConfig
import com.saveourtool.save.core.utils.singleIsInstanceOrNull
import com.saveourtool.save.core.utils.mergeWith
import com.saveourtool.save.core.utils.overrideBy

import okio.FileSystem
import okio.Path
Expand All @@ -20,14 +23,18 @@ import kotlin.js.JsName
* Configuration for a test suite, that is read from test suite configuration file (toml config)
* @property location [Path] denoting the location of this file
* @property parentConfig parent config in the hierarchy of configs, `null` if this config is root.
* @property evaluatedToolConfig a configuration for evaluated tool
* @property pluginConfigs list of configurations for plugins that are active in this config
* @property overridesPluginConfigs list of configurations for plugins that overrides [pluginConfigs]
* @property fs filesystem which can access test configs
*/
@Suppress("TYPE_ALIAS", "TooManyFunctions")
data class TestConfig(
val location: Path,
val parentConfig: TestConfig?,
val evaluatedToolConfig: EvaluatedToolConfig,
val pluginConfigs: MutableList<PluginConfig> = mutableListOf(),
val overridesPluginConfigs: List<PluginConfig>,
val fs: FileSystem,
) {
/**
Expand Down Expand Up @@ -78,7 +85,7 @@ data class TestConfig(
*
* @return [GeneralConfig] or `null` if not found
*/
fun getGeneralConfig() = pluginConfigs.filterIsInstance<GeneralConfig>().singleOrNull()
fun getGeneralConfig() = pluginConfigs.singleIsInstanceOrNull<GeneralConfig>()

/**
* @param withSelf if true, include this config as the first element of the sequence or start with parent config otherwise
Expand Down Expand Up @@ -127,10 +134,11 @@ data class TestConfig(
// discover plugins from the test configuration
createPluginConfigList(this).forEach {
logTrace("Discovered new pluginConfig: $it")
this.pluginConfigs.merge(it)
this.pluginConfigs.add(it)
}
// merge configurations with parents
this.mergeConfigWithParent()
overrideConfig()
return this
}

Expand Down Expand Up @@ -171,17 +179,18 @@ data class TestConfig(
fun mergeConfigWithParent(): TestConfig {
logDebug("Merging configs (with parental configs from higher directory level) for ${this.location}")

if (parentConfig != null) {
parentConfig?.let {
logTrace("Using parental config ${parentConfig.location} to merge it with child config: ${this.location}")
// return from the function if we stay at the root element of the plugin tree
val parentalPlugins = parentConfig.pluginConfigs
parentalPlugins.forEach { parentalPluginConfig ->
this.pluginConfigs.merge(parentalPluginConfig)
}
this.pluginConfigs.mergeWith(parentConfig.pluginConfigs)
}
return this
}

private fun overrideConfig() {
logDebug("Overriding configs for $location")
pluginConfigs.overrideBy(overridesPluginConfigs)
}

/**
* Method, which validates all plugin configs and set default values, if possible
*/
Expand All @@ -192,23 +201,6 @@ data class TestConfig(
logDebug("Validated plugin configuration for [$location] " +
"(${pluginConfigs.map { it.type }.filterNot { it == TestConfigSections.GENERAL }})")
}

private fun MutableList<PluginConfig>.merge(parentalPluginConfig: PluginConfig) {
val childConfigs = this.filter { it.type == parentalPluginConfig.type }
if (childConfigs.isEmpty()) {
// if we haven't found a plugin from parent in a current list of plugins - we will simply copy it
this.add(parentalPluginConfig)
} else {
require(childConfigs.size == 1) {
"Duplicate config with type ${parentalPluginConfig.type} in $this"
}
val childConfig = childConfigs.single()
// else, we will merge plugin with a corresponding plugin from a parent config
// we expect that there is only one plugin of such type, otherwise we will throw an exception
logTrace("Merging process of ${parentalPluginConfig.type} from $parentalPluginConfig into $childConfig")
this[this.indexOf(childConfig)] = childConfig.mergeWith(parentalPluginConfig)
}
}
}

/**
Expand All @@ -235,3 +227,8 @@ fun Path.isSaveTomlConfig() = name == "save.toml"
* @return a file (save.toml) in current directory
*/
fun Path.resolveSaveTomlConfig() = this / "save.toml"

/**
* @return a file (save-overrides.toml) in current directory
*/
fun Path.resolveSaveOverridesTomlConfig() = this / "save-overrides.toml"
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.saveourtool.save.core.plugin

import com.saveourtool.save.core.config.EvaluatedToolConfig
import com.saveourtool.save.core.config.TestConfig
import com.saveourtool.save.core.config.isSaveTomlConfig
import com.saveourtool.save.core.files.createRelativePathToTheRoot
Expand All @@ -12,6 +11,7 @@ import com.saveourtool.save.core.result.Ignored
import com.saveourtool.save.core.result.TestResult
import com.saveourtool.save.core.utils.PathSerializer
import com.saveourtool.save.core.utils.ProcessBuilder
import com.saveourtool.save.core.utils.singleIsInstanceOrNull

import okio.FileSystem
import okio.Path
Expand Down Expand Up @@ -44,17 +44,15 @@ abstract class Plugin(
/**
* Perform plugin's work.
*
* @param evaluatedToolConfig a configuration for evaluated tool
* @return a sequence of [TestResult]s for each group of test resources
*/
fun execute(evaluatedToolConfig: EvaluatedToolConfig): Sequence<TestResult> {
fun execute(): Sequence<TestResult> {
clean()
val testFilesList = discoverTestFiles(testConfig.directory).toList()

val excludedTests = testConfig
.pluginConfigs
.filterIsInstance<GeneralConfig>()
.singleOrNull()
.singleIsInstanceOrNull<GeneralConfig>()
?.excludedTests

if (!excludedTests.isNullOrEmpty()) {
Expand All @@ -71,7 +69,7 @@ abstract class Plugin(
val excludedTestResults = excludedTestFiles.map {
TestResult(it, Ignored("Excluded by configuration"))
}
handleFiles(evaluatedToolConfig, actualTestFiles.asSequence()) + excludedTestResults
handleFiles(actualTestFiles.asSequence()) + excludedTestResults
} else {
emptySequence()
}
Expand All @@ -80,11 +78,10 @@ abstract class Plugin(
/**
* Perform plugin's work on a set of files.
*
* @param evaluatedToolConfig a configuration for evaluated tool
* @param files a sequence of file groups, corresponding to tests.
* @return a sequence of [TestResult]s for each group of test resources
*/
abstract fun handleFiles(evaluatedToolConfig: EvaluatedToolConfig, files: Sequence<TestFiles>): Sequence<TestResult>
abstract fun handleFiles(files: Sequence<TestFiles>): Sequence<TestResult>

/**
* Discover groups of resource files which will be used to run tests, applying additional filtering
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import kotlinx.serialization.Transient
import kotlinx.serialization.UseSerializers

/**
* Core interface for plugin configuration (like warnPlugin/fixPluin/e.t.c)
* Core interface for plugin configuration (like warnPlugin/fixPlugin/e.t.c)
*/
interface PluginConfig {
/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* This file contains util methods for [PluginConfig]
*/

package com.saveourtool.save.core.utils

import com.saveourtool.save.core.logging.logTrace
import com.saveourtool.save.core.plugin.PluginConfig

/**
* @return original list of configuration for plugins after validation and merged with default values
*/
fun MutableList<PluginConfig>.validateAndSetDefaults() {
forEachIndexed { index, config ->
this[index] = config.validateAndSetDefaults()
}
}

/**
* @param otherPluginConfigs list of configurations for plugins that are merged to current list
* @return original list of configuration for plugins merged with [otherPluginConfigs]
*/
fun MutableList<PluginConfig>.mergeWith(otherPluginConfigs: List<PluginConfig>) {
otherPluginConfigs.forEach { otherPluginConfig ->
this.mergeOrOverride(otherPluginConfig)
}
}

/**
* @param otherPluginConfigs list of configurations for plugins that overrides current list
* @return original list of configuration for plugins overridden by [otherPluginConfigs]
*/
fun MutableList<PluginConfig>.overrideBy(otherPluginConfigs: List<PluginConfig>) {
otherPluginConfigs.forEach { otherPluginConfig ->
this.mergeOrOverride(otherPluginConfig, merge = false)
}
}

private fun MutableList<PluginConfig>.mergeOrOverride(otherPluginConfig: PluginConfig, merge: Boolean = true) {
val childConfigsWithIndex = this.withIndex().filter { (_, value) -> value.type == otherPluginConfig.type }
if (childConfigsWithIndex.isEmpty()) {
// if we haven't found a plugin from parent in a current list of plugins - we will simply copy it
this.add(otherPluginConfig)
} else {
require(childConfigsWithIndex.size == 1) {
"Duplicate config with type ${otherPluginConfig.type} in $this"
}
val (childIndex, childConfig) = childConfigsWithIndex.single()
// else, we will merge plugin with a corresponding plugin from a parent config
// we expect that there is only one plugin of such type, otherwise we will throw an exception

this[childIndex] = if (merge) {
logTrace("Merging process of ${otherPluginConfig.type} from $otherPluginConfig into $childConfig")
childConfig.mergeWith(otherPluginConfig)
} else {
logTrace("Overriding process of ${otherPluginConfig.type} from $otherPluginConfig into $childConfig")
otherPluginConfig.mergeWith(childConfig)
}
}
}

/**
* @return a single [PluginConfig] with type [P] from current list
*/
inline fun <reified P : PluginConfig> List<PluginConfig>.singleIsInstance() = requireNotNull(this.singleIsInstanceOrNull<P>()) {
"Not found an element with type ${P::class}"
}

/**
* @return a single [PluginConfig] with type [P] from current list or null
*/
inline fun <reified P : PluginConfig> List<PluginConfig>.singleIsInstanceOrNull() = this.filterIsInstance<P>().singleOrNull()