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 3 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
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
@@ -0,0 +1,20 @@
package com.saveourtool.save.core.config

/**
* An interface for interim result during reading from TOML file
*
* @param I class represents interim result
* @param R class represents result
*/
interface Interim<R, I : Interim<R, I>> {
/**
* @param overrides
* @return result with values are overridden by values from [overrides]
*/
fun merge(overrides: I) : I

/**
* @return result with values from interim object
*/
fun build(): R
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,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 @@ -127,10 +131,11 @@ data class TestConfig(
// discover plugins from the test configuration
createPluginConfigList(this).forEach {
logTrace("Discovered new pluginConfig: $it")
this.pluginConfigs.merge(it)
this.pluginConfigs.mergeOrOverride(it)
}
// merge configurations with parents
this.mergeConfigWithParent()
overrideConfig()
return this
}

Expand Down Expand Up @@ -176,12 +181,19 @@ data class TestConfig(
// 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.mergeOrOverride(parentalPluginConfig)
}
}
return this
}

private fun overrideConfig() {
logDebug("Overriding configs for $location")
overridesPluginConfigs.forEach { overridesPluginConfig ->
pluginConfigs.mergeOrOverride(overridesPluginConfig, false)
}
}

/**
* Method, which validates all plugin configs and set default values, if possible
*/
Expand All @@ -193,7 +205,7 @@ data class TestConfig(
"(${pluginConfigs.map { it.type }.filterNot { it == TestConfigSections.GENERAL }})")
}

private fun MutableList<PluginConfig>.merge(parentalPluginConfig: PluginConfig) {
private fun MutableList<PluginConfig>.mergeOrOverride(parentalPluginConfig: PluginConfig, merge: Boolean = true) {
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
Expand All @@ -205,8 +217,14 @@ data class TestConfig(
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)

this[this.indexOf(childConfig)] = if (merge) {
logTrace("Merging process of ${parentalPluginConfig.type} from $parentalPluginConfig into $childConfig")
childConfig.mergeWith(parentalPluginConfig)
} else {
logTrace("Overriding process of ${parentalPluginConfig.type} from $parentalPluginConfig into $childConfig")
parentalPluginConfig.mergeWith(childConfig)
}
}
}
}
Expand Down Expand Up @@ -235,3 +253,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
@@ -0,0 +1,2 @@
package com.saveourtool.save.core.config

Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
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.config.*
import com.saveourtool.save.core.files.createRelativePathToTheRoot
import com.saveourtool.save.core.files.findDescendantDirectoriesBy
import com.saveourtool.save.core.files.parentsWithSelf
Expand Down Expand Up @@ -44,10 +42,9 @@ 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()

Expand All @@ -71,7 +68,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 +77,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,14 @@
package com.saveourtool.save.core.plugin

import com.saveourtool.save.core.config.TestConfigSections
import okio.Path

interface PluginConfigOverrides {
val type: TestConfigSections
val execCmd: String?
val execFlags: String?

interface Factory<T : PluginConfigOverrides> {
fun read(path: Path): T
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
package com.saveourtool.save.core

import com.saveourtool.save.core.config.EvaluatedToolConfig
import com.saveourtool.save.core.config.OutputStreamType
import com.saveourtool.save.core.config.ReportType
import com.saveourtool.save.core.config.*
import com.saveourtool.save.core.config.SAVE_VERSION
import com.saveourtool.save.core.config.SaveProperties
import com.saveourtool.save.core.config.TestConfig
import com.saveourtool.save.core.config.isSaveTomlConfig
import com.saveourtool.save.core.config.resolveSaveTomlConfig
import com.saveourtool.save.core.files.ConfigDetector
import com.saveourtool.save.core.files.StdStreamsSink
import com.saveourtool.save.core.logging.logDebug
Expand All @@ -25,7 +19,9 @@ import com.saveourtool.save.core.result.Ignored
import com.saveourtool.save.core.result.Pass
import com.saveourtool.save.core.result.TestResult
import com.saveourtool.save.core.utils.buildActivePlugins
import com.saveourtool.save.core.utils.createPluginConfigListFromToml
import com.saveourtool.save.core.utils.processInPlace
import com.saveourtool.save.core.utils.readFromFile
import com.saveourtool.save.plugin.warn.WarnPluginConfig
import com.saveourtool.save.plugins.fix.FixPlugin
import com.saveourtool.save.plugins.fix.FixPluginConfig
Expand Down Expand Up @@ -75,14 +71,20 @@ class Save(

// create config for evaluated tool from cli args
val evaluatedToolConfig = EvaluatedToolConfig(
execCmd = saveProperties.overrideExecCmd,
execFlags = saveProperties.overrideExecFlags,
batchSize = saveProperties.batchSize,
batchSeparator = saveProperties.batchSeparator,
)

// create config for evaluated tool from cli args
val saveOverridesPath = testRootPath.resolveSaveOverridesTomlConfig()
val pluginConfigOverrides = if (fs.exists(saveOverridesPath)) {
createPluginConfigListFromToml(saveOverridesPath, fs)
} else {
emptyList()
}

// get all toml configs in file system
val testConfigs = ConfigDetector(fs)
val testConfigs = ConfigDetector(fs, evaluatedToolConfig, pluginConfigOverrides)
.configFromFile(rootTestConfigPath)
.getAllTestConfigsForFiles(requestedConfigs)
var atLeastOneExecutionProvided = false
Expand All @@ -106,7 +108,7 @@ class Save(
?.forEach {
atLeastOneExecutionProvided = true
// execute created plugins
executePlugin(evaluatedToolConfig, it, reporter)
executePlugin(it, reporter)
}
?.also {
reporter.onSuiteEnd(testConfig.getGeneralConfig()?.suiteName!!)
Expand Down Expand Up @@ -156,15 +158,14 @@ class Save(
}

private fun executePlugin(
evaluatedToolConfig: EvaluatedToolConfig,
plugin: Plugin,
reporter: Reporter
) {
reporter.onPluginInitialization(plugin)
logDebug("=> Executing plugin: ${plugin::class.simpleName} for [${plugin.testConfig.location}]")
reporter.onPluginExecutionStart(plugin)
try {
plugin.execute(evaluatedToolConfig)
plugin.execute()
.onEach { event ->
// calculate relative paths, because reporters don't need paths higher than root dir
val resourcesRelative = event.resources.withRelativePaths(testRootPath)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
package com.saveourtool.save.core.files

import com.saveourtool.save.core.config.TestConfig
import com.saveourtool.save.core.config.isSaveTomlConfig
import com.saveourtool.save.core.config.*
import com.saveourtool.save.core.logging.logDebug
import com.saveourtool.save.core.logging.logError
import com.saveourtool.save.core.logging.logTrace
import com.saveourtool.save.core.plugin.PluginConfig

import okio.FileSystem
import okio.Path

/**
* A class that is capable of discovering config files hierarchy.
*/
class ConfigDetector(private val fs: FileSystem) {
class ConfigDetector(
private val fs: FileSystem,
private val evaluatedToolConfig: EvaluatedToolConfig,
private val pluginConfigsOverrides: List<PluginConfig>,
) {
/**
* Try to create SAVE config file from [file].
* Try to create SAVE config file from [testConfig].
*
* @param testConfig - testing configuration (save.toml) from which SAVE config file should be built
* @return [TestConfig] or null if no suitable config file has been found.
Expand Down Expand Up @@ -61,6 +65,8 @@ class ConfigDetector(private val fs: FileSystem) {
TestConfig(
path,
parentConfig,
evaluatedToolConfig,
overridesPluginConfigs = pluginConfigsOverrides,
fs = fs,
)
)
Expand Down Expand Up @@ -111,6 +117,8 @@ class ConfigDetector(private val fs: FileSystem) {
return TestConfig(
file,
parentConfig,
evaluatedToolConfig,
overridesPluginConfigs = pluginConfigsOverrides,
fs = fs,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@ import com.akuleshov7.ktoml.exceptions.TomlDecodingException
import com.akuleshov7.ktoml.file.TomlFileReader
import com.akuleshov7.ktoml.parsers.TomlParser
import com.akuleshov7.ktoml.tree.TomlTable
import com.saveourtool.save.core.config.Interim
import com.saveourtool.save.core.files.fs
import com.saveourtool.save.core.plugin.PluginConfigOverrides
import okio.FileSystem
import okio.Path

import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.serializer
import okio.Path.Companion.toPath

private fun Path.testConfigFactory(table: TomlTable) =
when (table.fullTableName.uppercase().replace("\"", "")) {
Expand Down Expand Up @@ -90,3 +94,33 @@ fun getTopLevelTomlTables(testConfigPath: Path, fs: FileSystem): List<TomlTable>
.children
.filterIsInstance<TomlTable>()
.filter { !it.isSynthetic }

/**
* @param filePath path to the TOML file
* @param specificTableName table name to deserialize [T]
* @param generalTableName table name for default values of [T]
* @param I interim type of [T]
*/
inline fun <T, reified I : Interim<T, I>> readFromFile(
filePath: Path,
specificTableName: String,
generalTableName: String
): T {
val serializer = serializer<I>()
val tomlInputConfigGeneralConfig = TomlInputConfig(ignoreUnknownNames = true)
return fs.read(filePath) {
val content = generateSequence { this.readUtf8Line() }.toList()
val generic: I = if (generalTableName.isNotEmpty()) {
TomlFileReader.partiallyDecodeFromString(
serializer,
content,
generalTableName,
tomlInputConfigGeneralConfig
)
} else {
TomlFileReader.decodeFromString(serializer, content, tomlInputConfigGeneralConfig)
}
val specific: I = TomlFileReader.partiallyDecodeFromString(serializer, content, specificTableName, TomlInputConfig())
generic.merge(specific).build()
}
}
Loading