From 9c961d1ae2334fdc20aed010493b0bc308addf7e Mon Sep 17 00:00:00 2001 From: Tanmay Ranjan <42682768+Tan108@users.noreply.github.com> Date: Mon, 5 Aug 2024 13:24:49 +0530 Subject: [PATCH] Get configuration dynamically (#37) Co-authored-by: HARSHIT SINHA <47042785+hsinha610@users.noreply.github.com> --- .../kotlin/com/featurevisor/sdk/Conditions.kt | 2 +- .../testRunner/BenchmarkFeature.kt | 2 +- .../testRunner/CommandExecuter.kt | 12 +++ .../com/featurevisor/testRunner/Parser.kt | 4 + .../featurevisor/testRunner/TestExecuter.kt | 82 +++++++++++-------- .../featurevisor/testRunner/TestFeature.kt | 15 ++-- .../featurevisor/testRunner/TestSegment.kt | 9 +- .../com/featurevisor/testRunner/Utils.kt | 26 ++---- .../kotlin/com/featurevisor/types/Types.kt | 18 ++++ 9 files changed, 100 insertions(+), 70 deletions(-) diff --git a/src/main/kotlin/com/featurevisor/sdk/Conditions.kt b/src/main/kotlin/com/featurevisor/sdk/Conditions.kt index 0245c40..41c15dc 100644 --- a/src/main/kotlin/com/featurevisor/sdk/Conditions.kt +++ b/src/main/kotlin/com/featurevisor/sdk/Conditions.kt @@ -114,7 +114,7 @@ object Conditions { attributeValue is AttributeValue.StringValue && conditionValue is ConditionValue.ArrayValue -> { when (operator) { IN_ARRAY -> attributeValue.value in conditionValue.values - NOT_IN_ARRAY -> (attributeValue.value in conditionValue.values).not() + NOT_IN_ARRAY -> (attributeValue.value !in conditionValue.values) else -> false } } diff --git a/src/main/kotlin/com/featurevisor/testRunner/BenchmarkFeature.kt b/src/main/kotlin/com/featurevisor/testRunner/BenchmarkFeature.kt index f3a05c8..a4f0ca2 100644 --- a/src/main/kotlin/com/featurevisor/testRunner/BenchmarkFeature.kt +++ b/src/main/kotlin/com/featurevisor/testRunner/BenchmarkFeature.kt @@ -28,7 +28,7 @@ fun benchmarkFeature(option: BenchMarkOptions) { val datafileBuildStart = System.nanoTime().toDouble() - val datafileContent = buildDataFileForStaging(option.projectRootPath) + val datafileContent = buildDataFileAsPerEnvironment(option.projectRootPath,"staging") val datafileBuildEnd = System.nanoTime().toDouble() diff --git a/src/main/kotlin/com/featurevisor/testRunner/CommandExecuter.kt b/src/main/kotlin/com/featurevisor/testRunner/CommandExecuter.kt index 910a73f..4f8e88f 100644 --- a/src/main/kotlin/com/featurevisor/testRunner/CommandExecuter.kt +++ b/src/main/kotlin/com/featurevisor/testRunner/CommandExecuter.kt @@ -39,3 +39,15 @@ private fun String.runCommand(workingDir: File): String? = printMessageInRedColor("Exception while executing command -> ${e.message}") null } + +fun createCommandForConfiguration()= + "npx featurevisor config --print --pretty" + +fun getConfigurationJson(projectRootPath: String) = + try { + createCommandForConfiguration().runCommand(getFileForSpecificPath(projectRootPath)) + }catch (e:Exception){ + printMessageInRedColor("Exception in createCommandForConfiguration Commandline execution --> ${e.message}") + null + } + diff --git a/src/main/kotlin/com/featurevisor/testRunner/Parser.kt b/src/main/kotlin/com/featurevisor/testRunner/Parser.kt index 7b4b062..b3f140d 100644 --- a/src/main/kotlin/com/featurevisor/testRunner/Parser.kt +++ b/src/main/kotlin/com/featurevisor/testRunner/Parser.kt @@ -257,3 +257,7 @@ private fun parseConditionValue(value: Any?): ConditionValue { } } +fun parseConfiguration(projectRootPath: String) = + json.decodeFromString(Configuration.serializer(),getConfigurationJson(projectRootPath)!!) + + diff --git a/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt b/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt index 4ce8844..fec08f5 100644 --- a/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt +++ b/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt @@ -10,55 +10,60 @@ data class TestProjectOption( val showDatafile: Boolean = false, val onlyFailures: Boolean = false, val fast: Boolean = false, - val testDirPath: String = "tests", - val projectRootPath: String = getRootProjectDir() + val projectRootPath: String? = null ) -fun startTest(option: TestProjectOption) { +fun startTest(option: TestProjectOption) = option.projectRootPath?.let { it -> + val projectConfig = parseConfiguration(it) var hasError = false - val folder = File("${option.projectRootPath}/${option.testDirPath}") + val folder = File(projectConfig.testsDirectoryPath) val listOfFiles = folder.listFiles()?.sortedBy { it } - var executionResult: ExecutionResult? = null + var executionResult: ExecutionResult? val startTime = System.currentTimeMillis() var passedTestsCount = 0 var failedTestsCount = 0 var passedAssertionsCount = 0 var failedAssertionsCount = 0 + val datafileContentByEnvironment: MutableMap = mutableMapOf() - if (!listOfFiles.isNullOrEmpty()) { - val datafile = - if (option.fast) buildDataFileForBothEnvironments(projectRootPath = option.projectRootPath) else DataFile( - null, - null + if (option.fast) { + for (environment in projectConfig.environments) { + val datafileContent = buildDataFileAsPerEnvironment( + projectRootPath = it, + environment = environment ) - if (option.fast && (datafile.stagingDataFiles == null || datafile.productionDataFiles == null)) { - return + datafileContentByEnvironment[environment] = datafileContent } + } + + if (!listOfFiles.isNullOrEmpty()) { for (file in listOfFiles) { if (file.isFile) { if (file.extension.equals("yml", true)) { val filePath = file.absoluteFile.path - try { - executionResult = executeTest(filePath, dataFile = datafile, option) - } catch (e: Exception) { - printMessageInRedColor("Exception while execution test --> ${e.message}") - } - - if (executionResult == null) { - continue - } - - if (executionResult.passed) { - passedTestsCount++ + if (listOfFiles.isNotEmpty()) { + executionResult = try { + executeTest(filePath, datafileContentByEnvironment, option, projectConfig) + } catch (e: Exception) { + printMessageInRedColor("Exception while executing assertion -> ${e.message}") + null + } + if (executionResult == null) { + continue + } + + if (executionResult.passed) { + passedTestsCount++ + } else { + hasError = true + failedTestsCount++ + } + + passedAssertionsCount += executionResult.assertionsCount.passed + failedAssertionsCount += executionResult.assertionsCount.failed } else { - hasError = true - failedTestsCount++ + printMessageInRedColor("The file is not valid yml file") } - - passedAssertionsCount += executionResult.assertionsCount.passed - failedAssertionsCount += executionResult.assertionsCount.failed - } else { - printMessageInRedColor("The file is not valid yml file") } } } @@ -81,9 +86,15 @@ fun startTest(option: TestProjectOption) { } else { printMessageInRedColor("Directory is Empty or not exists") } -} +} ?: printNormalMessage("Root Project Path Not Found") + -private fun executeTest(filePath: String, dataFile: DataFile, option: TestProjectOption): ExecutionResult? { +private fun executeTest( + filePath: String, + datafileContentByEnvironment: MutableMap, + option: TestProjectOption, + configuration: Configuration +): ExecutionResult? { val test = parseTestFeatureAssertions(filePath) val executionResult = ExecutionResult( @@ -91,7 +102,7 @@ private fun executeTest(filePath: String, dataFile: DataFile, option: TestProjec assertionsCount = AssertionsCount(0, 0) ) - if (test != null){ + if (test != null) { val key = when (test) { is Test.Feature -> test.value.key is Test.Segment -> test.value.key @@ -105,7 +116,7 @@ private fun executeTest(filePath: String, dataFile: DataFile, option: TestProjec is Test.Feature -> { testFeature( testFeature = test.value, - dataFile = dataFile, + datafileContentByEnvironment = datafileContentByEnvironment, option = option ) } @@ -113,6 +124,7 @@ private fun executeTest(filePath: String, dataFile: DataFile, option: TestProjec is Test.Segment -> { testSegment( testSegment = test.value, + configuration = configuration, option = option ) } diff --git a/src/main/kotlin/com/featurevisor/testRunner/TestFeature.kt b/src/main/kotlin/com/featurevisor/testRunner/TestFeature.kt index d8fb584..379647d 100644 --- a/src/main/kotlin/com/featurevisor/testRunner/TestFeature.kt +++ b/src/main/kotlin/com/featurevisor/testRunner/TestFeature.kt @@ -9,7 +9,11 @@ import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement -fun testFeature(testFeature: TestFeature, dataFile: DataFile, option: TestProjectOption): TestResult { +fun testFeature( + testFeature: TestFeature, + datafileContentByEnvironment:MutableMap, + option: TestProjectOption +): TestResult { val testStartTime = System.currentTimeMillis() val featureKey = testFeature.key @@ -40,15 +44,12 @@ fun testFeature(testFeature: TestFeature, dataFile: DataFile, option: TestProjec return@forEach } - val datafileContent = if (option.fast) { - if (it.environment.equals("staging", true)) dataFile.stagingDataFiles else dataFile.productionDataFiles - } else { - getDataFileContent( + val datafileContent = datafileContentByEnvironment[it.environment] + ?: getDataFileContent( featureName = testFeature.key, environment = it.environment, - projectRootPath = option.projectRootPath + projectRootPath = option.projectRootPath.orEmpty() ) - } if (option.showDatafile) { printNormalMessage("") diff --git a/src/main/kotlin/com/featurevisor/testRunner/TestSegment.kt b/src/main/kotlin/com/featurevisor/testRunner/TestSegment.kt index a943a0d..f14be40 100644 --- a/src/main/kotlin/com/featurevisor/testRunner/TestSegment.kt +++ b/src/main/kotlin/com/featurevisor/testRunner/TestSegment.kt @@ -1,12 +1,9 @@ package com.featurevisor.testRunner import com.featurevisor.sdk.segmentIsMatched -import com.featurevisor.types.TestResult -import com.featurevisor.types.TestResultAssertion -import com.featurevisor.types.TestResultAssertionError -import com.featurevisor.types.TestSegment +import com.featurevisor.types.* -fun testSegment(testSegment: TestSegment, option: TestProjectOption): TestResult { +fun testSegment(testSegment: TestSegment,configuration: Configuration,option: TestProjectOption): TestResult { val testStartTime = System.currentTimeMillis() val segmentKey = testSegment.key @@ -36,7 +33,7 @@ fun testSegment(testSegment: TestSegment, option: TestProjectOption): TestResult return@forEach } - val yamlSegment = parseYamlSegment("${option.projectRootPath}/segments/$segmentKey.yml") + val yamlSegment = parseYamlSegment("${configuration.segmentsDirectoryPath}/$segmentKey.yml") val expected = it.expectedToMatch val actual = segmentIsMatched(yamlSegment!!, it.context) val passed = actual == expected diff --git a/src/main/kotlin/com/featurevisor/testRunner/Utils.kt b/src/main/kotlin/com/featurevisor/testRunner/Utils.kt index e140ca3..d83a328 100644 --- a/src/main/kotlin/com/featurevisor/testRunner/Utils.kt +++ b/src/main/kotlin/com/featurevisor/testRunner/Utils.kt @@ -2,6 +2,7 @@ package com.featurevisor.testRunner import com.featurevisor.sdk.FeaturevisorInstance import com.featurevisor.sdk.InstanceOptions +import com.featurevisor.sdk.emptyDatafile import com.featurevisor.types.* import kotlinx.serialization.decodeFromString import kotlinx.serialization.json.Json @@ -237,29 +238,14 @@ fun checkJsonIsEquals(a: String, b: String): Boolean { return map1 == map2 } -fun buildDataFileForBothEnvironments(projectRootPath: String): DataFile = - DataFile( - stagingDataFiles = buildDataFileForStaging(projectRootPath), - productionDataFiles = buildDataFileForProduction(projectRootPath) - ) - -fun buildDataFileForStaging(projectRootPath: String) = try { - getJsonForDataFile(environment = "staging", projectRootPath = projectRootPath)?.run { - convertToDataClass() - } -} catch (e: Exception) { - printMessageInRedColor("Unable to parse staging data file") - null -} -fun buildDataFileForProduction(projectRootPath: String) = try { - getJsonForDataFile(environment = "production", projectRootPath = projectRootPath)?.run { +fun buildDataFileAsPerEnvironment(projectRootPath: String,environment: String) = try { + getJsonForDataFile(environment = environment, projectRootPath = projectRootPath)?.run { convertToDataClass() - } - + } ?: emptyDatafile } catch (e: Exception) { - printMessageInRedColor("Unable to parse production data file") - null + printMessageInRedColor("Unable to parse data file") + emptyDatafile } fun getDataFileContent(featureName: String, environment: String, projectRootPath: String) = diff --git a/src/main/kotlin/com/featurevisor/types/Types.kt b/src/main/kotlin/com/featurevisor/types/Types.kt index 3357c32..dff5b19 100644 --- a/src/main/kotlin/com/featurevisor/types/Types.kt +++ b/src/main/kotlin/com/featurevisor/types/Types.kt @@ -434,3 +434,21 @@ data class DataFile( val stagingDataFiles: DatafileContent? = null, val productionDataFiles: DatafileContent? = null ) + +@Serializable +data class Configuration( + val environments:List, + val tags: List, + val defaultBucketBy:String, + val prettyState:Boolean, + val prettyDatafile:Boolean, + val stringify:Boolean, + val featuresDirectoryPath:String, + val segmentsDirectoryPath:String, + val attributesDirectoryPath:String, + val groupsDirectoryPath:String, + val testsDirectoryPath:String, + val stateDirectoryPath:String, + val outputDirectoryPath:String, + val siteExportDirectoryPath:String +)