From 2f8ef7ff11d2f197c5aa3c668b8accd7cd98d680 Mon Sep 17 00:00:00 2001 From: Tanmay Ranjan Date: Wed, 10 Apr 2024 14:14:13 +0530 Subject: [PATCH 01/12] get testDir and segmentDir using configuration --- .../testRunner/CommandExecuter.kt | 12 ++ .../com/featurevisor/testRunner/Parser.kt | 4 + .../featurevisor/testRunner/TestExecuter.kt | 127 +++++++++--------- .../featurevisor/testRunner/TestFeature.kt | 2 +- .../featurevisor/testRunner/TestSegment.kt | 9 +- .../kotlin/com/featurevisor/types/Types.kt | 18 +++ 6 files changed, 103 insertions(+), 69 deletions(-) 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 401a77a..8cb51d4 100644 --- a/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt +++ b/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt @@ -10,80 +10,83 @@ 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) { - var hasError = false - val folder = File("${option.projectRootPath}/${option.testDirPath}") - val listOfFiles = folder.listFiles() - var executionResult: ExecutionResult? = null - val startTime = System.currentTimeMillis() - var passedTestsCount = 0 - var failedTestsCount = 0 - var passedAssertionsCount = 0 - var failedAssertionsCount = 0 - - if (!listOfFiles.isNullOrEmpty()) { - val datafile = - if (option.fast) buildDataFileForBothEnvironments(projectRootPath = option.projectRootPath) else DataFile( - null, - null - ) - if (option.fast && (datafile.stagingDataFiles == null || datafile.productionDataFiles == null)) { - return - } - 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 in $filePath --> ${e.message}") - } - - if (executionResult == null) { - continue - } - - if (executionResult.passed) { - passedTestsCount++ + option.projectRootPath?.let { + val configurations = parseConfiguration(option.projectRootPath) + var hasError = false + val folder = File(configurations.testsDirectoryPath) + val listOfFiles = folder.listFiles() + var executionResult: ExecutionResult? = null + val startTime = System.currentTimeMillis() + var passedTestsCount = 0 + var failedTestsCount = 0 + var passedAssertionsCount = 0 + var failedAssertionsCount = 0 + + if (!listOfFiles.isNullOrEmpty()) { + val datafile = + if (option.fast) buildDataFileForBothEnvironments(projectRootPath = option.projectRootPath) else DataFile( + null, + null + ) + if (option.fast && (datafile.stagingDataFiles == null || datafile.productionDataFiles == null)) { + return + } + 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, configurations) + } catch (e: Exception) { + printMessageInRedColor("Exception in $filePath --> ${e.message}") + } + + 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") } } - } - - val endTime = System.currentTimeMillis() - startTime - if (!option.onlyFailures || hasError) { - printNormalMessage("\n----") - } - printNormalMessage("") + val endTime = System.currentTimeMillis() - startTime - if (hasError) { - printMessageInRedColor("\n\nTest specs: $passedTestsCount passed, $failedTestsCount failed") - printMessageInRedColor("Test Assertion: $passedAssertionsCount passed, $failedAssertionsCount failed") + if (!option.onlyFailures || hasError) { + printNormalMessage("\n----") + } + printNormalMessage("") + + if (hasError) { + printMessageInRedColor("\n\nTest specs: $passedTestsCount passed, $failedTestsCount failed") + printMessageInRedColor("Test Assertion: $passedAssertionsCount passed, $failedAssertionsCount failed") + } else { + printMessageInGreenColor("\n\nTest specs: $passedTestsCount passed, $failedTestsCount failed") + printMessageInGreenColor("Test Assertion: $passedAssertionsCount passed, $failedAssertionsCount failed") + } + printBoldMessage("Time: ${prettyDuration(endTime)}") } else { - printMessageInGreenColor("\n\nTest specs: $passedTestsCount passed, $failedTestsCount failed") - printMessageInGreenColor("Test Assertion: $passedAssertionsCount passed, $failedAssertionsCount failed") + printMessageInRedColor("Directory is Empty or not exists") } - printBoldMessage("Time: ${prettyDuration(endTime)}") - } 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, dataFile: DataFile, option: TestProjectOption,configuration: Configuration): ExecutionResult? { val test = parseTestFeatureAssertions(filePath) val executionResult = ExecutionResult( @@ -107,7 +110,7 @@ private fun executeTest(filePath: String, dataFile: DataFile, option: TestProjec } is Test.Segment -> { - testSegment(test.value, option.projectRootPath) + testSegment(test.value, configuration) } } diff --git a/src/main/kotlin/com/featurevisor/testRunner/TestFeature.kt b/src/main/kotlin/com/featurevisor/testRunner/TestFeature.kt index d8fb584..ed9a257 100644 --- a/src/main/kotlin/com/featurevisor/testRunner/TestFeature.kt +++ b/src/main/kotlin/com/featurevisor/testRunner/TestFeature.kt @@ -46,7 +46,7 @@ fun testFeature(testFeature: TestFeature, dataFile: DataFile, option: TestProjec getDataFileContent( featureName = testFeature.key, environment = it.environment, - projectRootPath = option.projectRootPath + projectRootPath = option.projectRootPath.orEmpty() ) } diff --git a/src/main/kotlin/com/featurevisor/testRunner/TestSegment.kt b/src/main/kotlin/com/featurevisor/testRunner/TestSegment.kt index c26bfcc..78ab75e 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(test: TestSegment, segmentFilePath: String): TestResult { +fun testSegment(test: TestSegment,configuration: Configuration): TestResult { val testStartTime = System.currentTimeMillis() val segmentKey = test.key @@ -32,7 +29,7 @@ fun testSegment(test: TestSegment, segmentFilePath: String): TestResult { errors = mutableListOf() ) - val yamlSegment = parseYamlSegment("$segmentFilePath/segments/$segmentKey.yml") + val yamlSegment = parseYamlSegment("${configuration.segmentsDirectoryPath}/$segmentKey.yml") val expected = assertion.expectedToMatch val actual = segmentIsMatched(yamlSegment!!, assertion.context) val passed = actual == expected 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 +) From b96d9a5b68797f9cd4686e17325615c7c4f99d0b Mon Sep 17 00:00:00 2001 From: Tanmay Ranjan Date: Wed, 10 Apr 2024 15:32:36 +0530 Subject: [PATCH 02/12] environments are implemented using project config --- .../testRunner/BenchmarkFeature.kt | 2 +- .../featurevisor/testRunner/TestExecuter.kt | 64 +++++++++---------- .../featurevisor/testRunner/TestFeature.kt | 24 +++++-- .../featurevisor/testRunner/TestSegment.kt | 2 +- .../com/featurevisor/testRunner/Utils.kt | 26 ++------ 5 files changed, 57 insertions(+), 61 deletions(-) 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/TestExecuter.kt b/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt index efe0159..61dc111 100644 --- a/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt +++ b/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt @@ -14,10 +14,10 @@ data class TestProjectOption( ) fun startTest(option: TestProjectOption) { - option.projectRootPath?.let { - val configurations = parseConfiguration(option.projectRootPath) + option.projectRootPath?.let { it -> + val projectConfig = parseConfiguration(it) var hasError = false - val folder = File(configurations.testsDirectoryPath) + val folder = File(projectConfig.testsDirectoryPath) val listOfFiles = folder.listFiles()?.sortedBy { it } var executionResult: ExecutionResult? = null val startTime = System.currentTimeMillis() @@ -25,39 +25,36 @@ fun startTest(option: TestProjectOption) { 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 && (datafile.stagingDataFiles == null || datafile.productionDataFiles == null)) { - return + if (option.fast) { + for (environment in projectConfig.environments) { + val datafileContent = buildDataFileAsPerEnvironment(option.projectRootPath,environment) + 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, configurations) - } catch (e: Exception) { - printMessageInRedColor("Exception in $filePath --> ${e.message}") + if (listOfFiles.isNotEmpty()){ + executionResult = executeTest(filePath, datafileContentByEnvironment, option, projectConfig) + if (executionResult == null) { + continue + } + + if (executionResult.passed) { + passedTestsCount++ + } else { + hasError = true + failedTestsCount++ + } + + passedAssertionsCount += executionResult.assertionsCount.passed + failedAssertionsCount += executionResult.assertionsCount.failed } - - if (executionResult == null) { - continue - } - - if (executionResult.passed) { - passedTestsCount++ - } else { - hasError = true - failedTestsCount++ - } - - passedAssertionsCount += executionResult.assertionsCount.passed - failedAssertionsCount += executionResult.assertionsCount.failed } else { printMessageInRedColor("The file is not valid yml file") } @@ -86,7 +83,7 @@ fun startTest(option: TestProjectOption) { } -private fun executeTest(filePath: String, dataFile: DataFile, option: TestProjectOption,configuration: Configuration): ExecutionResult? { +private fun executeTest(filePath: String, datafileContentByEnvironment:MutableMap, option: TestProjectOption,configuration: Configuration): ExecutionResult? { val test = parseTestFeatureAssertions(filePath) val executionResult = ExecutionResult( @@ -108,15 +105,16 @@ private fun executeTest(filePath: String, dataFile: DataFile, option: TestProjec is Test.Feature -> { testFeature( testFeature = test.value, - dataFile = dataFile, + datafileContentByEnvironment = datafileContentByEnvironment, option = option ) } is Test.Segment -> { testSegment( - test.value, - configuration + 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 ed9a257..2f78537 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,23 @@ 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.orEmpty() ) - } + + +// if (option.fast) { +// if (it.environment.equals("staging", true)) dataFile.stagingDataFiles else dataFile.productionDataFiles +// } else { +// getDataFileContent( +// featureName = testFeature.key, +// environment = it.environment, +// 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 8fe852d..f14be40 100644 --- a/src/main/kotlin/com/featurevisor/testRunner/TestSegment.kt +++ b/src/main/kotlin/com/featurevisor/testRunner/TestSegment.kt @@ -3,7 +3,7 @@ package com.featurevisor.testRunner import com.featurevisor.sdk.segmentIsMatched import com.featurevisor.types.* -fun testSegment(testSegment: TestSegment,configuration: Configuration): TestResult { +fun testSegment(testSegment: TestSegment,configuration: Configuration,option: TestProjectOption): TestResult { val testStartTime = System.currentTimeMillis() val segmentKey = testSegment.key 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) = From e004d5e6d4afbaec81a5d3a483a58012a9e47c15 Mon Sep 17 00:00:00 2001 From: Tanmay Ranjan <42682768+Tan108@users.noreply.github.com> Date: Sat, 11 May 2024 00:17:28 +0530 Subject: [PATCH 03/12] Json type variable parsing issue fixes (#40) --- src/main/kotlin/com/featurevisor/sdk/Instance+Evaluation.kt | 2 +- src/main/kotlin/com/featurevisor/sdk/Instance+Variable.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/featurevisor/sdk/Instance+Evaluation.kt b/src/main/kotlin/com/featurevisor/sdk/Instance+Evaluation.kt index dc65951..e6881d8 100644 --- a/src/main/kotlin/com/featurevisor/sdk/Instance+Evaluation.kt +++ b/src/main/kotlin/com/featurevisor/sdk/Instance+Evaluation.kt @@ -475,7 +475,7 @@ fun FeaturevisorInstance.evaluateVariable( // initial if (!statuses.ready && initialFeatures?.get(featureKey)?.variables?.get(variableKey) != null) { - val variableValue = initialFeatures?.get(featureKey)?.variables?.get(variableKey) + val variableValue = initialFeatures[featureKey]?.variables?.get(variableKey) evaluation = Evaluation( featureKey = featureKey, reason = INITIAL, diff --git a/src/main/kotlin/com/featurevisor/sdk/Instance+Variable.kt b/src/main/kotlin/com/featurevisor/sdk/Instance+Variable.kt index 925f20d..c14b5e3 100644 --- a/src/main/kotlin/com/featurevisor/sdk/Instance+Variable.kt +++ b/src/main/kotlin/com/featurevisor/sdk/Instance+Variable.kt @@ -84,7 +84,7 @@ inline fun FeaturevisorInstance.getVariableObject( } } -inline fun FeaturevisorInstance.getVariableJSON( +inline fun FeaturevisorInstance.getVariableJSON( featureKey: FeatureKey, variableKey: VariableKey, context: Context, From fe077def3b9c7416dc376f89e8ec05f2cd7287fa Mon Sep 17 00:00:00 2001 From: HARSHIT SINHA <47042785+hsinha610@users.noreply.github.com> Date: Sat, 11 May 2024 16:27:30 +0530 Subject: [PATCH 04/12] =?UTF-8?q?Added:=20upperBound=20`Any`=20to=20type?= =?UTF-8?q?=20parameter=20`T`=20in=20getVariableJSON=20func=E2=80=A6=20(#4?= =?UTF-8?q?1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/com/featurevisor/sdk/Instance+Variable.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/featurevisor/sdk/Instance+Variable.kt b/src/main/kotlin/com/featurevisor/sdk/Instance+Variable.kt index c14b5e3..30fda18 100644 --- a/src/main/kotlin/com/featurevisor/sdk/Instance+Variable.kt +++ b/src/main/kotlin/com/featurevisor/sdk/Instance+Variable.kt @@ -84,7 +84,7 @@ inline fun FeaturevisorInstance.getVariableObject( } } -inline fun FeaturevisorInstance.getVariableJSON( +inline fun FeaturevisorInstance.getVariableJSON( featureKey: FeatureKey, variableKey: VariableKey, context: Context, From 455e13e86fa87bb3339876db9e64650023cdfe73 Mon Sep 17 00:00:00 2001 From: HARSHIT SINHA <47042785+hsinha610@users.noreply.github.com> Date: Fri, 12 Jul 2024 18:16:40 +0530 Subject: [PATCH 05/12] Made getVariation method public (#45) --- src/main/kotlin/com/featurevisor/sdk/Instance+Variation.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/featurevisor/sdk/Instance+Variation.kt b/src/main/kotlin/com/featurevisor/sdk/Instance+Variation.kt index 5a9eb2a..a340c1d 100644 --- a/src/main/kotlin/com/featurevisor/sdk/Instance+Variation.kt +++ b/src/main/kotlin/com/featurevisor/sdk/Instance+Variation.kt @@ -4,7 +4,7 @@ import com.featurevisor.types.Context import com.featurevisor.types.FeatureKey import com.featurevisor.types.VariationValue -internal fun FeaturevisorInstance.getVariation(featureKey: FeatureKey, context: Context): VariationValue? { +fun FeaturevisorInstance.getVariation(featureKey: FeatureKey, context: Context): VariationValue? { val evaluation = evaluateVariation(featureKey, context) return when { evaluation.variationValue != null -> evaluation.variationValue From ab978baf7e5a471f10d3848fa71d6689b4483c52 Mon Sep 17 00:00:00 2001 From: HARSHIT SINHA <47042785+hsinha610@users.noreply.github.com> Date: Thu, 25 Jul 2024 16:46:52 +0530 Subject: [PATCH 06/12] Fixed: Exception Handling in Evaluation (#46) --- src/main/kotlin/com/featurevisor/sdk/Instance+Evaluation.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/featurevisor/sdk/Instance+Evaluation.kt b/src/main/kotlin/com/featurevisor/sdk/Instance+Evaluation.kt index e6881d8..0946458 100644 --- a/src/main/kotlin/com/featurevisor/sdk/Instance+Evaluation.kt +++ b/src/main/kotlin/com/featurevisor/sdk/Instance+Evaluation.kt @@ -640,14 +640,13 @@ fun FeaturevisorInstance.evaluateVariable( logger?.debug("using default value", evaluation.toDictionary()) return evaluation } - }catch (e: Exception){ + } catch (e: Exception){ evaluation = Evaluation( featureKey = featureKey, reason = ERROR, - error(e) ) - this.logger?.error("error", evaluation.toDictionary()) + this.logger?.error(message = e.message.orEmpty(), details = evaluation.toDictionary()) return evaluation } From d13cbeb7e18a429ff8f3c19ea25f78b279cb030b Mon Sep 17 00:00:00 2001 From: Tanmay Ranjan <42682768+Tan108@users.noreply.github.com> Date: Mon, 5 Aug 2024 11:43:25 +0530 Subject: [PATCH 07/12] Test Runner failed in case of invalid Semver version issue fixes (#49) --- src/main/kotlin/com/featurevisor/sdk/Conditions.kt | 6 +++++- src/main/kotlin/com/featurevisor/sdk/Instance+Evaluation.kt | 6 ++---- src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/featurevisor/sdk/Conditions.kt b/src/main/kotlin/com/featurevisor/sdk/Conditions.kt index 2bda4ec..0245c40 100644 --- a/src/main/kotlin/com/featurevisor/sdk/Conditions.kt +++ b/src/main/kotlin/com/featurevisor/sdk/Conditions.kt @@ -142,6 +142,10 @@ object Conditions { } private fun compareVersions(actual: String, condition: String): Int { - return SemVer.parse(actual).compareTo(SemVer.parse(condition)) + return try { + SemVer.parse(actual).compareTo(SemVer.parse(condition)) + } catch (e: Exception) { + 0 + } } } diff --git a/src/main/kotlin/com/featurevisor/sdk/Instance+Evaluation.kt b/src/main/kotlin/com/featurevisor/sdk/Instance+Evaluation.kt index 0946458..9e3cef9 100644 --- a/src/main/kotlin/com/featurevisor/sdk/Instance+Evaluation.kt +++ b/src/main/kotlin/com/featurevisor/sdk/Instance+Evaluation.kt @@ -220,10 +220,9 @@ fun FeaturevisorInstance.evaluateVariation(featureKey: FeatureKey, context: Cont evaluation = Evaluation( featureKey = featureKey, reason = ERROR, - error(e) ) - this.logger?.error("error", evaluation.toDictionary()) + this.logger?.error(message = e.message.orEmpty(), details = evaluation.toDictionary()) return evaluation } @@ -433,10 +432,9 @@ fun FeaturevisorInstance.evaluateFlag(featureKey: FeatureKey, context: Context = evaluation = Evaluation( featureKey = featureKey, reason = ERROR, - error(e) ) - this.logger?.error("error", evaluation.toDictionary()) + this.logger?.error(message = e.message.orEmpty(), details = evaluation.toDictionary()) return evaluation } diff --git a/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt b/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt index b2e9f69..4ce8844 100644 --- a/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt +++ b/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt @@ -41,7 +41,7 @@ fun startTest(option: TestProjectOption) { try { executionResult = executeTest(filePath, dataFile = datafile, option) } catch (e: Exception) { - printMessageInRedColor("Exception in $filePath --> ${e.message}") + printMessageInRedColor("Exception while execution test --> ${e.message}") } if (executionResult == null) { From 129dd69c8a12c3da13251f9fc81a40441e1dc6b0 Mon Sep 17 00:00:00 2001 From: Tanmay Ranjan Date: Wed, 10 Apr 2024 14:14:13 +0530 Subject: [PATCH 08/12] get testDir and segmentDir using configuration --- .../testRunner/CommandExecuter.kt | 12 +++ .../com/featurevisor/testRunner/Parser.kt | 4 + .../featurevisor/testRunner/TestExecuter.kt | 88 +++++++++---------- .../featurevisor/testRunner/TestFeature.kt | 2 +- .../featurevisor/testRunner/TestSegment.kt | 13 ++- .../kotlin/com/featurevisor/types/Types.kt | 18 ++++ 6 files changed, 84 insertions(+), 53 deletions(-) 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..128bd9d 100644 --- a/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt +++ b/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt @@ -10,39 +10,40 @@ 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) { - var hasError = false - val folder = File("${option.projectRootPath}/${option.testDirPath}") - val listOfFiles = folder.listFiles()?.sortedBy { it } - var executionResult: ExecutionResult? = null - val startTime = System.currentTimeMillis() - var passedTestsCount = 0 - var failedTestsCount = 0 - var passedAssertionsCount = 0 - var failedAssertionsCount = 0 - - if (!listOfFiles.isNullOrEmpty()) { - val datafile = - if (option.fast) buildDataFileForBothEnvironments(projectRootPath = option.projectRootPath) else DataFile( - null, - null - ) - if (option.fast && (datafile.stagingDataFiles == null || datafile.productionDataFiles == null)) { - return - } - 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}") - } + option.projectRootPath?.let { + val configurations = parseConfiguration(option.projectRootPath) + var hasError = false + val folder = File(configurations.testsDirectoryPath) + val listOfFiles = folder.listFiles() + var executionResult: ExecutionResult? = null + val startTime = System.currentTimeMillis() + var passedTestsCount = 0 + var failedTestsCount = 0 + var passedAssertionsCount = 0 + var failedAssertionsCount = 0 + + if (!listOfFiles.isNullOrEmpty()) { + val datafile = + if (option.fast) buildDataFileForBothEnvironments(projectRootPath = option.projectRootPath) else DataFile( + null, + null + ) + if (option.fast && (datafile.stagingDataFiles == null || datafile.productionDataFiles == null)) { + return + } + 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, configurations) + } catch (e: Exception) { + printMessageInRedColor("Exception in $filePath --> ${e.message}") + } if (executionResult == null) { continue @@ -70,20 +71,22 @@ fun startTest(option: TestProjectOption) { } printNormalMessage("") - if (hasError) { - printMessageInRedColor("\n\nTest specs: $passedTestsCount passed, $failedTestsCount failed") - printMessageInRedColor("Test Assertion: $passedAssertionsCount passed, $failedAssertionsCount failed") + if (hasError) { + printMessageInRedColor("\n\nTest specs: $passedTestsCount passed, $failedTestsCount failed") + printMessageInRedColor("Test Assertion: $passedAssertionsCount passed, $failedAssertionsCount failed") + } else { + printMessageInGreenColor("\n\nTest specs: $passedTestsCount passed, $failedTestsCount failed") + printMessageInGreenColor("Test Assertion: $passedAssertionsCount passed, $failedAssertionsCount failed") + } + printBoldMessage("Time: ${prettyDuration(endTime)}") } else { - printMessageInGreenColor("\n\nTest specs: $passedTestsCount passed, $failedTestsCount failed") - printMessageInGreenColor("Test Assertion: $passedAssertionsCount passed, $failedAssertionsCount failed") + printMessageInRedColor("Directory is Empty or not exists") } - printBoldMessage("Time: ${prettyDuration(endTime)}") - } 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, dataFile: DataFile, option: TestProjectOption,configuration: Configuration): ExecutionResult? { val test = parseTestFeatureAssertions(filePath) val executionResult = ExecutionResult( @@ -111,10 +114,7 @@ private fun executeTest(filePath: String, dataFile: DataFile, option: TestProjec } is Test.Segment -> { - testSegment( - testSegment = test.value, - option = option - ) + testSegment(test.value, configuration) } } diff --git a/src/main/kotlin/com/featurevisor/testRunner/TestFeature.kt b/src/main/kotlin/com/featurevisor/testRunner/TestFeature.kt index d8fb584..ed9a257 100644 --- a/src/main/kotlin/com/featurevisor/testRunner/TestFeature.kt +++ b/src/main/kotlin/com/featurevisor/testRunner/TestFeature.kt @@ -46,7 +46,7 @@ fun testFeature(testFeature: TestFeature, dataFile: DataFile, option: TestProjec getDataFileContent( featureName = testFeature.key, environment = it.environment, - projectRootPath = option.projectRootPath + projectRootPath = option.projectRootPath.orEmpty() ) } diff --git a/src/main/kotlin/com/featurevisor/testRunner/TestSegment.kt b/src/main/kotlin/com/featurevisor/testRunner/TestSegment.kt index a943a0d..45c5853 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): TestResult { val testStartTime = System.currentTimeMillis() val segmentKey = testSegment.key @@ -36,9 +33,9 @@ fun testSegment(testSegment: TestSegment, option: TestProjectOption): TestResult return@forEach } - val yamlSegment = parseYamlSegment("${option.projectRootPath}/segments/$segmentKey.yml") - val expected = it.expectedToMatch - val actual = segmentIsMatched(yamlSegment!!, it.context) + val yamlSegment = parseYamlSegment("${configuration.segmentsDirectoryPath}/$segmentKey.yml") + val expected = assertion.expectedToMatch + val actual = segmentIsMatched(yamlSegment!!, assertion.context) val passed = actual == expected if (!passed) { 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 +) From 9116ace15ea8b4c19f9c2beaa0b84cf18fad4ec4 Mon Sep 17 00:00:00 2001 From: Tanmay Ranjan Date: Wed, 10 Apr 2024 15:32:36 +0530 Subject: [PATCH 09/12] environments are implemented using project config --- .../testRunner/BenchmarkFeature.kt | 2 +- .../featurevisor/testRunner/TestExecuter.kt | 75 ++++++++++--------- .../featurevisor/testRunner/TestFeature.kt | 24 ++++-- .../featurevisor/testRunner/TestSegment.kt | 2 +- .../com/featurevisor/testRunner/Utils.kt | 26 ++----- 5 files changed, 64 insertions(+), 65 deletions(-) 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/TestExecuter.kt b/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt index 128bd9d..00e9faa 100644 --- a/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt +++ b/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt @@ -14,62 +14,58 @@ data class TestProjectOption( ) fun startTest(option: TestProjectOption) { - option.projectRootPath?.let { - val configurations = parseConfiguration(option.projectRootPath) + option.projectRootPath?.let { it -> + val projectConfig = parseConfiguration(it) var hasError = false - val folder = File(configurations.testsDirectoryPath) - val listOfFiles = folder.listFiles() + val folder = File(projectConfig.testsDirectoryPath) + val listOfFiles = folder.listFiles()?.sortedBy { it } var executionResult: ExecutionResult? = null 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 && (datafile.stagingDataFiles == null || datafile.productionDataFiles == null)) { - return + if (option.fast) { + for (environment in projectConfig.environments) { + val datafileContent = buildDataFileAsPerEnvironment(option.projectRootPath,environment) + 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, configurations) - } catch (e: Exception) { - printMessageInRedColor("Exception in $filePath --> ${e.message}") + if (listOfFiles.isNotEmpty()){ + executionResult = executeTest(filePath, datafileContentByEnvironment, option, projectConfig) + if (executionResult == null) { + continue + } + + if (executionResult.passed) { + passedTestsCount++ + } else { + hasError = true + failedTestsCount++ } - if (executionResult == null) { - continue - } - - if (executionResult.passed) { - passedTestsCount++ + 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") } } - } - val endTime = System.currentTimeMillis() - startTime + val endTime = System.currentTimeMillis() - startTime - if (!option.onlyFailures || hasError) { - printNormalMessage("\n----") - } - printNormalMessage("") + if (!option.onlyFailures || hasError) { + printNormalMessage("\n----") + } + printNormalMessage("") if (hasError) { printMessageInRedColor("\n\nTest specs: $passedTestsCount passed, $failedTestsCount failed") @@ -86,7 +82,7 @@ fun startTest(option: TestProjectOption) { } -private fun executeTest(filePath: String, dataFile: DataFile, option: TestProjectOption,configuration: Configuration): ExecutionResult? { +private fun executeTest(filePath: String, datafileContentByEnvironment:MutableMap, option: TestProjectOption,configuration: Configuration): ExecutionResult? { val test = parseTestFeatureAssertions(filePath) val executionResult = ExecutionResult( @@ -108,12 +104,17 @@ private fun executeTest(filePath: String, dataFile: DataFile, option: TestProjec is Test.Feature -> { testFeature( testFeature = test.value, - dataFile = dataFile, + datafileContentByEnvironment = datafileContentByEnvironment, option = option ) } is Test.Segment -> { +// testSegment( +// testSegment = test.value, +// configuration = configuration, +// option = option +// ) testSegment(test.value, configuration) } } diff --git a/src/main/kotlin/com/featurevisor/testRunner/TestFeature.kt b/src/main/kotlin/com/featurevisor/testRunner/TestFeature.kt index ed9a257..2f78537 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,23 @@ 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.orEmpty() ) - } + + +// if (option.fast) { +// if (it.environment.equals("staging", true)) dataFile.stagingDataFiles else dataFile.productionDataFiles +// } else { +// getDataFileContent( +// featureName = testFeature.key, +// environment = it.environment, +// 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 45c5853..c6ef7e4 100644 --- a/src/main/kotlin/com/featurevisor/testRunner/TestSegment.kt +++ b/src/main/kotlin/com/featurevisor/testRunner/TestSegment.kt @@ -3,7 +3,7 @@ package com.featurevisor.testRunner import com.featurevisor.sdk.segmentIsMatched import com.featurevisor.types.* -fun testSegment(testSegment: TestSegment,configuration: Configuration): TestResult { +fun testSegment(testSegment: TestSegment,configuration: Configuration,option: TestProjectOption): TestResult { val testStartTime = System.currentTimeMillis() val segmentKey = testSegment.key 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) = From 041a505f90556c4d39b0eaa6ee0b013fabbf22bb Mon Sep 17 00:00:00 2001 From: Tanmay Ranjan Date: Mon, 5 Aug 2024 12:43:33 +0530 Subject: [PATCH 10/12] get configuration dynamically handling --- .../featurevisor/testRunner/TestExecuter.kt | 133 ++++++++++-------- 1 file changed, 72 insertions(+), 61 deletions(-) diff --git a/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt b/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt index 89efdce..fec08f5 100644 --- a/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt +++ b/src/main/kotlin/com/featurevisor/testRunner/TestExecuter.kt @@ -13,44 +13,51 @@ data class TestProjectOption( val projectRootPath: String? = null ) -fun startTest(option: TestProjectOption) { - option.projectRootPath?.let { it -> - val projectConfig = parseConfiguration(it) - var hasError = false - val folder = File(projectConfig.testsDirectoryPath) - val listOfFiles = folder.listFiles()?.sortedBy { it } - var executionResult: ExecutionResult? = null - val startTime = System.currentTimeMillis() - var passedTestsCount = 0 - var failedTestsCount = 0 - var passedAssertionsCount = 0 - var failedAssertionsCount = 0 - val datafileContentByEnvironment: MutableMap = mutableMapOf() - - if (option.fast) { - for (environment in projectConfig.environments) { - val datafileContent = buildDataFileAsPerEnvironment(option.projectRootPath,environment) - datafileContentByEnvironment[environment] = datafileContent - } +fun startTest(option: TestProjectOption) = option.projectRootPath?.let { it -> + val projectConfig = parseConfiguration(it) + var hasError = false + val folder = File(projectConfig.testsDirectoryPath) + val listOfFiles = folder.listFiles()?.sortedBy { it } + var executionResult: ExecutionResult? + val startTime = System.currentTimeMillis() + var passedTestsCount = 0 + var failedTestsCount = 0 + var passedAssertionsCount = 0 + var failedAssertionsCount = 0 + val datafileContentByEnvironment: MutableMap = mutableMapOf() + + if (option.fast) { + for (environment in projectConfig.environments) { + val datafileContent = buildDataFileAsPerEnvironment( + projectRootPath = it, + environment = environment + ) + datafileContentByEnvironment[environment] = datafileContent } + } - if (!listOfFiles.isNullOrEmpty()) { - for (file in listOfFiles) { - if (file.isFile) { - if (file.extension.equals("yml", true)) { - val filePath = file.absoluteFile.path - if (listOfFiles.isNotEmpty()){ - executionResult = executeTest(filePath, datafileContentByEnvironment, option, projectConfig) - if (executionResult == null) { - continue - } - - if (executionResult.passed) { - passedTestsCount++ - } else { - hasError = true - failedTestsCount++ - } + if (!listOfFiles.isNullOrEmpty()) { + for (file in listOfFiles) { + if (file.isFile) { + if (file.extension.equals("yml", true)) { + val filePath = file.absoluteFile.path + 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 @@ -59,30 +66,35 @@ fun startTest(option: TestProjectOption) { } } } + } - val endTime = System.currentTimeMillis() - startTime + val endTime = System.currentTimeMillis() - startTime - if (!option.onlyFailures || hasError) { - printNormalMessage("\n----") - } - printNormalMessage("") - - if (hasError) { - printMessageInRedColor("\n\nTest specs: $passedTestsCount passed, $failedTestsCount failed") - printMessageInRedColor("Test Assertion: $passedAssertionsCount passed, $failedAssertionsCount failed") - } else { - printMessageInGreenColor("\n\nTest specs: $passedTestsCount passed, $failedTestsCount failed") - printMessageInGreenColor("Test Assertion: $passedAssertionsCount passed, $failedAssertionsCount failed") - } - printBoldMessage("Time: ${prettyDuration(endTime)}") + if (!option.onlyFailures || hasError) { + printNormalMessage("\n----") + } + printNormalMessage("") + + if (hasError) { + printMessageInRedColor("\n\nTest specs: $passedTestsCount passed, $failedTestsCount failed") + printMessageInRedColor("Test Assertion: $passedAssertionsCount passed, $failedAssertionsCount failed") } else { - printMessageInRedColor("Directory is Empty or not exists") + printMessageInGreenColor("\n\nTest specs: $passedTestsCount passed, $failedTestsCount failed") + printMessageInGreenColor("Test Assertion: $passedAssertionsCount passed, $failedAssertionsCount failed") } - } ?: printNormalMessage("Root Project Path Not Found") + printBoldMessage("Time: ${prettyDuration(endTime)}") + } else { + printMessageInRedColor("Directory is Empty or not exists") + } +} ?: printNormalMessage("Root Project Path Not Found") -} -private fun executeTest(filePath: String, datafileContentByEnvironment:MutableMap, option: TestProjectOption,configuration: Configuration): ExecutionResult? { +private fun executeTest( + filePath: String, + datafileContentByEnvironment: MutableMap, + option: TestProjectOption, + configuration: Configuration +): ExecutionResult? { val test = parseTestFeatureAssertions(filePath) val executionResult = ExecutionResult( @@ -90,7 +102,7 @@ private fun executeTest(filePath: String, datafileContentByEnvironment:MutableMa 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 @@ -110,12 +122,11 @@ private fun executeTest(filePath: String, datafileContentByEnvironment:MutableMa } is Test.Segment -> { -// testSegment( -// testSegment = test.value, -// configuration = configuration, -// option = option -// ) - testSegment(test.value, configuration) + testSegment( + testSegment = test.value, + configuration = configuration, + option = option + ) } } From cd6fd313a53ab734a7840023525971a89657a8fe Mon Sep 17 00:00:00 2001 From: Tanmay Ranjan Date: Mon, 5 Aug 2024 13:14:12 +0530 Subject: [PATCH 11/12] remove not to ! for checking NOT_IN_ARRAY condition --- src/main/kotlin/com/featurevisor/sdk/Conditions.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 } } From eba95357635398f9910f72f765a8a8a4f887daea Mon Sep 17 00:00:00 2001 From: Tanmay Ranjan Date: Mon, 5 Aug 2024 13:19:02 +0530 Subject: [PATCH 12/12] remove commented line --- .../kotlin/com/featurevisor/testRunner/TestFeature.kt | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/src/main/kotlin/com/featurevisor/testRunner/TestFeature.kt b/src/main/kotlin/com/featurevisor/testRunner/TestFeature.kt index 2f78537..379647d 100644 --- a/src/main/kotlin/com/featurevisor/testRunner/TestFeature.kt +++ b/src/main/kotlin/com/featurevisor/testRunner/TestFeature.kt @@ -51,17 +51,6 @@ fun testFeature( projectRootPath = option.projectRootPath.orEmpty() ) - -// if (option.fast) { -// if (it.environment.equals("staging", true)) dataFile.stagingDataFiles else dataFile.productionDataFiles -// } else { -// getDataFileContent( -// featureName = testFeature.key, -// environment = it.environment, -// projectRootPath = option.projectRootPath.orEmpty() -// ) -// } - if (option.showDatafile) { printNormalMessage("") printNormalMessage(datafileContent.toString())