diff --git a/java-agent/build.gradle.kts b/java-agent/build.gradle.kts index 79f47be4..9fc34e08 100644 --- a/java-agent/build.gradle.kts +++ b/java-agent/build.gradle.kts @@ -74,6 +74,7 @@ kotlin { languageSettings.optIn("kotlin.ExperimentalStdlibApi") languageSettings.optIn("kotlinx.serialization.ExperimentalSerializationApi") languageSettings.optIn("io.ktor.utils.io.core.ExperimentalIoApi") + languageSettings.optIn("kotlinx.serialization.InternalSerializationApi") } targets.withType()[HostManager.host.presetName].compilations.forEach { it.defaultSourceSet.kotlin.srcDir("src/native${it.compilationName.capitalize()}/kotlin") @@ -115,6 +116,7 @@ kotlin { implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:${kotlinxSerializationVersion}") implementation("org.jetbrains.kotlinx:kotlinx-serialization-protobuf:${kotlinxSerializationVersion}") implementation("org.jetbrains.kotlinx:kotlinx-cli:${kotlinxCliVersion}") + implementation("com.benasher44:uuid:${uuidVersion}") implementation(project(":common")) implementation(project(":agent-transport")) implementation(project(":agent-instrumentation")) diff --git a/java-agent/src/commonMain/kotlin/com/epam/drill/agent/configuration/ParameterDefinitions.kt b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/configuration/ParameterDefinitions.kt index 058faaea..8879b1f7 100644 --- a/java-agent/src/commonMain/kotlin/com/epam/drill/agent/configuration/ParameterDefinitions.kt +++ b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/configuration/ParameterDefinitions.kt @@ -44,4 +44,40 @@ object ParameterDefinitions: AgentParameterDefinitionCollection() { val USE_PROTOBUF_SERIALIZER = AgentParameterDefinition.forBoolean(name = "useProtobufSerializer", defaultValue = true).register() val USE_GZIP_COMPRESSION = AgentParameterDefinition.forBoolean(name = "useGzipCompression", defaultValue = true).register() + + val TEST_AGENT_ENABLED = AgentParameterDefinition.forBoolean(name = "testAgentEnabled", defaultValue = false).register() + + val WITH_JS_COVERAGE = AgentParameterDefinition.forBoolean(name = "withJsCoverage", defaultValue = false).register() + val PROXY_ADDRESS = AgentParameterDefinition.forString(name = "browserProxyAddress", defaultValue = "").register() + val DEVTOOLS_PROXY_ADDRESS = AgentParameterDefinition.forString( + name = "devToolsProxyAddress", + defaultValue = "http://localhost:9222",//TODO + parser = { it.trim().takeIf(String::isBlank) ?: it.takeIf(URL_SCHEME_REGEX::matches) ?: "http://$it"} + ).register() + val DEVTOOLS_REPLACE_LOCALHOST = AgentParameterDefinition.forString(name = "devtoolsAddressReplaceLocalhost", defaultValue = "").register() + val SESSION_ID = NullableAgentParameterDefinition.forString(name = "sessionId").register() + val LAUNCH_TYPE = AgentParameterDefinition.forString(name = "launchType", defaultValue = "").register() + val FRAMEWORK_PLUGINS = AgentParameterDefinition.forType( + name = "rawFrameworkPlugins", + defaultValue = emptyList(), + parser = { it.split(";") } + ).register() + + private val URL_SCHEME_REGEX = Regex("\\w+://.+") + + val JS_AGENT_BUILD_VERSION = NullableAgentParameterDefinition.forString(name = "jsAgentBuildVersion").register() + val JS_AGENT_ID = NullableAgentParameterDefinition.forString(name = "jsAgentId").register() + + val TEST_TASK_ID = AgentParameterDefinition.forString(name = "testTaskId", defaultValue = "").register() + val RECOMMENDED_TESTS_ENABLED = AgentParameterDefinition.forBoolean(name = "recommendedTestsEnabled", defaultValue = false).register() + val RECOMMENDED_TESTS_COVERAGE_PERIOD_DAYS = AgentParameterDefinition.forInt(name = "recommendedTestsCoveragePeriodDays", defaultValue = 0).register() + val RECOMMENDED_TESTS_TARGET_APP_ID = AgentParameterDefinition.forString(name = "recommendedTestsTargetAppId", defaultValue = "").register() + val RECOMMENDED_TESTS_TARGET_COMMIT_SHA = AgentParameterDefinition.forString(name = "recommendedTestsTargetCommitSha", defaultValue = "").register() + val RECOMMENDED_TESTS_TARGET_BUILD_VERSION = AgentParameterDefinition.forString(name = "recommendedTestsTargetBuildVersion", defaultValue = "").register() + val RECOMMENDED_TESTS_BASELINE_COMMIT_SHA = AgentParameterDefinition.forString(name = "recommendedTestsBaselineCommitSha", defaultValue = "").register() + val RECOMMENDED_TESTS_BASELINE_BUILD_VERSION = AgentParameterDefinition.forString(name = "recommendedTestsBaselineBuildVersion", defaultValue = "").register() + val RECOMMENDED_TESTS_USE_MATERIALIZED_VIEWS = AgentParameterDefinition.forString(name = "recommendedTestsUseMaterializedViews", defaultValue = "").register() + + val TEST_TRACING_ENABLED = AgentParameterDefinition.forBoolean(name = "testTracingEnabled", defaultValue = true).register() + val TEST_LAUNCH_METADATA_SENDING_ENABLED = AgentParameterDefinition.forBoolean(name = "testLaunchMetadataSendingEnabled", defaultValue = true).register() } diff --git a/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber4Transformer.kt b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber4Transformer.kt new file mode 100644 index 00000000..5ffa4f42 --- /dev/null +++ b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber4Transformer.kt @@ -0,0 +1,20 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.cucumber + +import com.epam.drill.agent.instrument.TransformerObject + +expect object Cucumber4Transformer : TransformerObject \ No newline at end of file diff --git a/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber5Transformer.kt b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber5Transformer.kt new file mode 100644 index 00000000..00327b71 --- /dev/null +++ b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber5Transformer.kt @@ -0,0 +1,20 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.cucumber + +import com.epam.drill.agent.instrument.TransformerObject + +expect object Cucumber5Transformer : TransformerObject \ No newline at end of file diff --git a/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber6Transformer.kt b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber6Transformer.kt new file mode 100644 index 00000000..275e6dff --- /dev/null +++ b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber6Transformer.kt @@ -0,0 +1,20 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.cucumber + +import com.epam.drill.agent.instrument.TransformerObject + +expect object Cucumber6Transformer : TransformerObject \ No newline at end of file diff --git a/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/jmeter/JMeterTransformer.kt b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/jmeter/JMeterTransformer.kt new file mode 100644 index 00000000..50d7aeec --- /dev/null +++ b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/jmeter/JMeterTransformer.kt @@ -0,0 +1,20 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.jmeter + +import com.epam.drill.agent.instrument.TransformerObject + +expect object JMeterTransformer : TransformerObject \ No newline at end of file diff --git a/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit4PrioritizingTransformer.kt b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit4PrioritizingTransformer.kt new file mode 100644 index 00000000..fa485db6 --- /dev/null +++ b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit4PrioritizingTransformer.kt @@ -0,0 +1,20 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.junit + +import com.epam.drill.agent.instrument.TransformerObject + +expect object JUnit4PrioritizingTransformer : TransformerObject \ No newline at end of file diff --git a/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit4Transformer.kt b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit4Transformer.kt new file mode 100644 index 00000000..3f2e3940 --- /dev/null +++ b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit4Transformer.kt @@ -0,0 +1,20 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.junit + +import com.epam.drill.agent.instrument.TransformerObject + +expect object JUnit4Transformer : TransformerObject \ No newline at end of file diff --git a/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit5Transformer.kt b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit5Transformer.kt new file mode 100644 index 00000000..07b99ef2 --- /dev/null +++ b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit5Transformer.kt @@ -0,0 +1,20 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.junit + +import com.epam.drill.agent.instrument.TransformerObject + +expect object JUnit5Transformer : TransformerObject \ No newline at end of file diff --git a/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnitPlatformPrioritizingTransformer.kt b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnitPlatformPrioritizingTransformer.kt new file mode 100644 index 00000000..e1d6aa68 --- /dev/null +++ b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnitPlatformPrioritizingTransformer.kt @@ -0,0 +1,20 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.junit + +import com.epam.drill.agent.instrument.TransformerObject + +expect object JUnitPlatformPrioritizingTransformer : TransformerObject \ No newline at end of file diff --git a/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/selenium/SeleniumTransformer.kt b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/selenium/SeleniumTransformer.kt new file mode 100644 index 00000000..9eb4dcf1 --- /dev/null +++ b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/selenium/SeleniumTransformer.kt @@ -0,0 +1,20 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.selenium + +import com.epam.drill.agent.instrument.TransformerObject + +expect object SeleniumTransformer : TransformerObject \ No newline at end of file diff --git a/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG6PrioritizingTransformer.kt b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG6PrioritizingTransformer.kt new file mode 100644 index 00000000..5112b468 --- /dev/null +++ b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG6PrioritizingTransformer.kt @@ -0,0 +1,20 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.testng + +import com.epam.drill.agent.instrument.TransformerObject + +expect object TestNG6PrioritizingTransformer: TransformerObject \ No newline at end of file diff --git a/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG6Transformer.kt b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG6Transformer.kt new file mode 100644 index 00000000..109223d7 --- /dev/null +++ b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG6Transformer.kt @@ -0,0 +1,20 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.testng + +import com.epam.drill.agent.instrument.TransformerObject + +expect object TestNG6Transformer: TransformerObject \ No newline at end of file diff --git a/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG7PrioritizingTransformer.kt b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG7PrioritizingTransformer.kt new file mode 100644 index 00000000..074a58b2 --- /dev/null +++ b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG7PrioritizingTransformer.kt @@ -0,0 +1,20 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.testng + +import com.epam.drill.agent.instrument.TransformerObject + +expect object TestNG7PrioritizingTransformer: TransformerObject \ No newline at end of file diff --git a/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG7Transformer.kt b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG7Transformer.kt new file mode 100644 index 00000000..c18b6af1 --- /dev/null +++ b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG7Transformer.kt @@ -0,0 +1,20 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.testng + +import com.epam.drill.agent.instrument.TransformerObject + +expect object TestNG7Transformer: TransformerObject \ No newline at end of file diff --git a/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/session/SessionController.kt b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/session/SessionController.kt new file mode 100644 index 00000000..bb6309f9 --- /dev/null +++ b/java-agent/src/commonMain/kotlin/com/epam/drill/agent/test/session/SessionController.kt @@ -0,0 +1,20 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.session + +expect object SessionController { + fun startSession() +} diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/Agent.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/Agent.kt index 597cef82..555698f5 100644 --- a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/Agent.kt +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/Agent.kt @@ -51,6 +51,20 @@ import com.epam.drill.agent.instrument.undertow.UndertowWsMessagesTransformer import com.epam.drill.agent.instrument.undertow.UndertowWsServerTransformer import com.epam.drill.agent.logging.LoggingConfiguration import com.epam.drill.agent.module.JvmModuleLoader +import com.epam.drill.agent.test.instrument.cucumber.Cucumber4Transformer +import com.epam.drill.agent.test.instrument.cucumber.Cucumber5Transformer +import com.epam.drill.agent.test.instrument.cucumber.Cucumber6Transformer +import com.epam.drill.agent.test.instrument.jmeter.JMeterTransformer +import com.epam.drill.agent.test.instrument.junit.JUnit4PrioritizingTransformer +import com.epam.drill.agent.test.instrument.junit.JUnit4Transformer +import com.epam.drill.agent.test.instrument.junit.JUnit5Transformer +import com.epam.drill.agent.test.instrument.junit.JUnitPlatformPrioritizingTransformer +import com.epam.drill.agent.test.instrument.selenium.SeleniumTransformer +import com.epam.drill.agent.test.instrument.testng.TestNG6PrioritizingTransformer +import com.epam.drill.agent.test.instrument.testng.TestNG6Transformer +import com.epam.drill.agent.test.instrument.testng.TestNG7PrioritizingTransformer +import com.epam.drill.agent.test.instrument.testng.TestNG7Transformer +import com.epam.drill.agent.test.session.SessionController import com.epam.drill.agent.test2code.Test2Code import com.epam.drill.agent.test2code.configuration.Test2CodeParameterDefinitions import com.epam.drill.agent.transport.JvmModuleMessageSender @@ -103,6 +117,19 @@ private val transformers = setOf( UndertowWsServerTransformer, UndertowWsMessagesTransformer, CompatibilityTestsTransformer, + JUnit4Transformer, + JUnit5Transformer, + TestNG6Transformer, + TestNG7Transformer, + Cucumber4Transformer, + Cucumber5Transformer, + Cucumber6Transformer, + SeleniumTransformer, + JMeterTransformer, + JUnit4PrioritizingTransformer, + JUnitPlatformPrioritizingTransformer, + TestNG6PrioritizingTransformer, + TestNG7PrioritizingTransformer, ) fun premain(agentArgs: String?, inst: Instrumentation) { @@ -116,6 +143,8 @@ fun premain(agentArgs: String?, inst: Instrumentation) { inst.addTransformer(DrillClassFileTransformer, true) JvmModuleMessageSender.sendAgentMetadata() JvmModuleLoader.loadJvmModule(Test2Code::class.java.name).load() + + SessionController.startSession() } catch (e: Throwable) { println("Drill4J Initialization Error:\n${e.message ?: e::class.java.name}") } diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/GlobalConstants.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/GlobalConstants.kt new file mode 100644 index 00000000..b2c468f9 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/GlobalConstants.kt @@ -0,0 +1,34 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test + +import com.epam.drill.agent.test.instrument.selenium.* +import com.epam.drill.agent.test.session.SessionController +import com.epam.drill.agent.test.execution.TestController +import kotlin.jvm.java + +const val TEST_ID_HEADER = "drill-test-id" +const val SESSION_ID_HEADER = "drill-session-id" + +val TEST_NAME_VALUE_CALC_LINE = "((String)${TestController::class.qualifiedName}.INSTANCE.${TestController::getTestLaunchId.name}())" +val TEST_NAME_CALC_LINE = "\"$TEST_ID_HEADER\", $TEST_NAME_VALUE_CALC_LINE" +val SESSION_ID_VALUE_CALC_LINE = "${SessionController::class.qualifiedName}.INSTANCE.${SessionController::getSessionId.name}()" +val SESSION_ID_CALC_LINE = "\"$SESSION_ID_HEADER\", $SESSION_ID_VALUE_CALC_LINE" +val IF_CONDITION = "$TEST_NAME_VALUE_CALC_LINE != null && $SESSION_ID_VALUE_CALC_LINE != null" +val DEV_TOOL = "((${ChromeDevTool::class.java.name})${DevToolStorage::class.java.name}.INSTANCE.${DevToolStorage::get.name}())" +val IS_DEV_TOOL_NOT_NULL = "$DEV_TOOL != null" +val IS_HEADER_ADDED = "($DEV_TOOL != null && $DEV_TOOL.${ChromeDevTool::isHeadersAdded.name}())" +val ARE_DRILL_HEADERS_PRESENT = "$TEST_NAME_VALUE_CALC_LINE != null && $SESSION_ID_VALUE_CALC_LINE != null" diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/devtools/AddSessionData.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/devtools/AddSessionData.kt new file mode 100644 index 00000000..c99ff883 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/devtools/AddSessionData.kt @@ -0,0 +1,22 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.devtools + +import com.epam.drill.agent.common.transport.AgentMessage +import kotlinx.serialization.Serializable + +@Serializable +data class AddSessionData(val sessionId: String, val data: String): AgentMessage() \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/devtools/ChromeDevToolTestExecutionListener.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/devtools/ChromeDevToolTestExecutionListener.kt new file mode 100644 index 00000000..99786650 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/devtools/ChromeDevToolTestExecutionListener.kt @@ -0,0 +1,41 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.devtools + +import com.epam.drill.agent.test.instrument.selenium.DevToolStorage +import com.epam.drill.agent.test.instrument.selenium.WebDriverThreadStorage +import com.epam.drill.agent.test.execution.TestExecutionListener +import com.epam.drill.agent.test.execution.TestMethodInfo +import com.epam.drill.agent.test.execution.TestResult + +class ChromeDevToolTestExecutionListener( + private val jsCoverageSender: JsCoverageSender +): TestExecutionListener { + + override fun onTestStarted(testLaunchId: String, test: TestMethodInfo) { + DevToolStorage.get()?.startIntercept() + WebDriverThreadStorage.addCookies() + } + + override fun onTestFinished(testLaunchId: String, test: TestMethodInfo, result: TestResult) { + DevToolStorage.get()?.stopIntercept() + jsCoverageSender.sendJsCoverage(testLaunchId) + } + + override fun onTestIgnored(testLaunchId: String, test: TestMethodInfo) { + DevToolStorage.get()?.stopIntercept() + } +} \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/devtools/DevToolsMessageSender.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/devtools/DevToolsMessageSender.kt new file mode 100644 index 00000000..00bc632e --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/devtools/DevToolsMessageSender.kt @@ -0,0 +1,93 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.devtools + +import mu.KotlinLogging +import com.epam.drill.agent.transport.JsonAgentMessageSerializer +import com.epam.drill.agent.transport.http.HttpAgentMessageTransport +import com.epam.drill.agent.common.transport.AgentMessage +import com.epam.drill.agent.common.transport.AgentMessageDestination +import com.epam.drill.agent.common.transport.ResponseStatus +import com.epam.drill.agent.configuration.Configuration +import com.epam.drill.agent.configuration.ParameterDefinitions +import com.epam.drill.agent.test.instrument.selenium.DevToolsMessage +import com.epam.drill.agent.transport.JsonAgentMessageDeserializer +import kotlin.reflect.KClass + +object DevToolsMessageSender { + + private val messageTransport = HttpAgentMessageTransport( + serverAddress = Configuration.parameters[ParameterDefinitions.DEVTOOLS_PROXY_ADDRESS], + drillInternal = false, + gzipCompression = false, + ) + private val messageSerializer = JsonAgentMessageSerializer() + private val messageDeserializer = JsonAgentMessageDeserializer() + private val logger = KotlinLogging.logger {} + + fun send( + method: String, + path: String, + message: DevToolsMessage + ) = messageTransport.send( + AgentMessageDestination(method, path), + messageSerializer.serialize(message, DevToolsMessage.serializer()), + messageSerializer.contentType() + ) + .mapContent { it.decodeToString() } + .also(DevToolsMessageSender::logResponseContent) + + fun send( + method: String, + path: String, + message: DevToolsMessage, + clazz: KClass + ) = messageTransport.send( + AgentMessageDestination(method, path), + messageSerializer.serialize(message, DevToolsMessage.serializer()), + messageSerializer.contentType() + ) + .mapContent { + messageDeserializer.deserialize(it, clazz) + } + .also(DevToolsMessageSender::logResponseContent) + + fun send( + serverAddress: String, + method: String, + path: String, + message: String + ): ResponseStatus = HttpAgentMessageTransport( + serverAddress = serverAddress, + drillInternal = false, + gzipCompression = false, + ).send( + AgentMessageDestination(method, path), + message.encodeToByteArray(), + messageSerializer.contentType() + ) + .mapContent { it.decodeToString() } + .also(DevToolsMessageSender::logResponseContent) + + private fun logResponseContent(responseContent: ResponseStatus<*>) = logger.trace { + val response = responseContent.content.toString() + .takeIf(String::isNotEmpty) + ?.let { "\n${it.prependIndent("\t")}" } + ?: " " + "send: Response received, success=${responseContent.success}: $response" + } + +} diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/devtools/JsCoverageSender.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/devtools/JsCoverageSender.kt new file mode 100644 index 00000000..eac17689 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/devtools/JsCoverageSender.kt @@ -0,0 +1,65 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.devtools + +import com.epam.drill.agent.common.transport.AgentMessageDestination +import com.epam.drill.agent.test.instrument.selenium.* +import com.epam.drill.agent.test.serialization.json +import com.epam.drill.agent.test.session.SessionController +import com.epam.drill.agent.test.transport.TestAgentMessageSender +import mu.KotlinLogging + +interface JsCoverageSender { + fun sendJsCoverage(testLaunchId: String) +} + +class JsCoverageSenderImpl : JsCoverageSender { + private val logger = KotlinLogging.logger {} + + override fun sendJsCoverage(testLaunchId: String) { + DevToolStorage.get()?.run { + val coverage = takePreciseCoverage() + if (coverage.isBlank()) { + logger.trace { "coverage is blank" } + return + } + val scripts = scriptParsed() + if (scripts.isBlank()) { + logger.trace { "script parsed is blank" } + return + } + logger.debug { "ThreadStorage.sendSessionData" } + sendSessionData(SessionData(coverage, scripts, testLaunchId)) + } + } + + private fun sendSessionData(data: SessionData) = runCatching { + val payload = AddSessionData( + sessionId = SessionController.getSessionId(), + data = json.encodeToString(SessionData.serializer(), data) + ) + TestAgentMessageSender.send( + destination = AgentMessageDestination( + "POST", + "raw-javascript-coverage" + ), + message = payload, + serializer = AddSessionData.serializer() + ) + }.onFailure { + logger.warn(it) { "can't send js raw coverage ${SessionController.getSessionId()}" } + }.getOrNull() +} \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/devtools/SessionData.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/devtools/SessionData.kt new file mode 100644 index 00000000..078c0740 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/devtools/SessionData.kt @@ -0,0 +1,25 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.devtools + +import kotlinx.serialization.* + +@Serializable +data class SessionData( + val coverage: String, + val scripts: String, + val testId: String, +) diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/execution/TestController.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/execution/TestController.kt new file mode 100644 index 00000000..794554cb --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/execution/TestController.kt @@ -0,0 +1,115 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.execution + +import com.epam.drill.agent.request.DrillRequestHolder +import com.epam.drill.agent.configuration.Configuration +import com.epam.drill.agent.configuration.ParameterDefinitions +import com.epam.drill.agent.test.TEST_ID_HEADER +import com.epam.drill.agent.test.devtools.ChromeDevToolTestExecutionListener +import com.epam.drill.agent.test.devtools.JsCoverageSenderImpl + +object TestController : TestExecutionRecorder by testExecutionRecorder(testExecutionListeners()) { + + //TODO EPMDJ-10251 add browser name for ui tests + @JvmOverloads + fun testStarted( + engine: String, + className: String?, + method: String?, + methodParams: String = "()", + testTags: List = emptyList(), + ) { + if (className == null || method == null) + return + + recordTestStarting( + TestMethodInfo( + engine = engine, + className = className, + method = method, + methodParams = methodParams, + tags = testTags + ) + ) + } + + @JvmOverloads + fun testFinished( + engine: String, + className: String?, + method: String?, + status: String, + methodParams: String = "()", + testTags: List = emptyList(), + ) { + if (className == null || method == null) + return + + recordTestFinishing( + TestMethodInfo( + engine = engine, + className = className, + method = method, + methodParams = methodParams, + tags = testTags + ), + status + ) + } + + @JvmOverloads + fun testIgnored( + engine: String, + className: String?, + method: String?, + methodParams: String = "()", + testTags: List = emptyList(), + ) { + if (className == null || method == null) + return + + recordTestIgnoring( + TestMethodInfo( + engine = engine, + className = className, + method = method, + methodParams = methodParams, + tags = testTags + ) + ) + } + + @Deprecated("Use explicit retrieve() instead", ReplaceWith("retrieve()")) + fun getTestLaunchId(): String? = DrillRequestHolder.retrieve()?.headers?.get(TEST_ID_HEADER) +} + +fun testExecutionListeners(): List { + val listeners = arrayListOf() + if (Configuration.parameters[ParameterDefinitions.WITH_JS_COVERAGE]) { + listeners.add( + ChromeDevToolTestExecutionListener( + jsCoverageSender = JsCoverageSenderImpl() + ) + ) + } + return listeners +} + +fun testExecutionRecorder(listeners: List) = ThreadTestExecutionRecorder( + requestHolder = DrillRequestHolder, + listeners = listeners +) \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/execution/TestExecutionInfo.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/execution/TestExecutionInfo.kt new file mode 100644 index 00000000..81260b95 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/execution/TestExecutionInfo.kt @@ -0,0 +1,24 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.execution + +class TestExecutionInfo( + val testLaunchId: String, + val testMethod: TestMethodInfo, + var result: TestResult = TestResult.UNKNOWN, + var startedAt: Long? = null, + var finishedAt: Long? = null, +) \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/execution/TestExecutionListener.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/execution/TestExecutionListener.kt new file mode 100644 index 00000000..c5caa637 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/execution/TestExecutionListener.kt @@ -0,0 +1,22 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.execution + +interface TestExecutionListener { + fun onTestStarted(testLaunchId: String, test: TestMethodInfo) {} + fun onTestFinished(testLaunchId: String, test: TestMethodInfo, result: TestResult) {} + fun onTestIgnored(testLaunchId: String, test: TestMethodInfo) {} +} \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/execution/TestExecutionRecorder.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/execution/TestExecutionRecorder.kt new file mode 100644 index 00000000..d6fc9f68 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/execution/TestExecutionRecorder.kt @@ -0,0 +1,36 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.execution + +interface TestExecutionRecorder { + fun recordTestStarting( + testMethod: TestMethodInfo + ) + + fun recordTestFinishing( + testMethod: TestMethodInfo, + status: String + ) + + fun recordTestIgnoring( + testMethod: TestMethodInfo, + isSmartSkip: Boolean = false + ) + + fun getFinishedTests(): List + + fun reset() +} \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/execution/TestMethodInfo.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/execution/TestMethodInfo.kt new file mode 100644 index 00000000..7b2280b2 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/execution/TestMethodInfo.kt @@ -0,0 +1,45 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.execution + +data class TestMethodInfo @JvmOverloads constructor( + val engine: String, + val className: String, + val method: String, + val methodParams: String = "()", + val metadata: Map = emptyMap(), + val tags: List = emptyList(), +) : Comparable { + + val signature: String + get() = "$engine:$className.$method${methodParams}" + + override fun compareTo(other: TestMethodInfo): Int { + return signature.compareTo(other.signature) + } + + override fun equals(other: Any?): Boolean { + return if (other is TestMethodInfo) compareTo(other) == 0 else false + } + + override fun hashCode(): Int { + return signature.hashCode() + } + + override fun toString(): String { + return signature + } +} \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/execution/TestResult.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/execution/TestResult.kt new file mode 100644 index 00000000..eef459ae --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/execution/TestResult.kt @@ -0,0 +1,25 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.execution + +enum class TestResult { + PASSED, + FAILED, + ERROR, + SKIPPED, + SMART_SKIPPED, + UNKNOWN +} \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/execution/ThreadTestExecutionRecorder.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/execution/ThreadTestExecutionRecorder.kt new file mode 100644 index 00000000..5e7dd616 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/execution/ThreadTestExecutionRecorder.kt @@ -0,0 +1,135 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.execution + +import com.epam.drill.agent.common.request.DrillRequest +import com.epam.drill.agent.common.request.RequestHolder +import com.epam.drill.agent.test.TEST_ID_HEADER +import com.epam.drill.agent.configuration.Configuration +import com.epam.drill.agent.configuration.ParameterDefinitions +import com.epam.drill.agent.test.session.SessionController +import mu.KotlinLogging +import java.util.* +import java.util.concurrent.ConcurrentHashMap + +class ThreadTestExecutionRecorder( + private val requestHolder: RequestHolder, + private val listeners: List = emptyList() +) : TestExecutionRecorder { + private val logger = KotlinLogging.logger {} + private val testExecutionData: ConcurrentHashMap = ConcurrentHashMap() + private val testLaunchHolder: ThreadLocal = ThreadLocal.withInitial { null } + + override fun recordTestStarting( + testMethod: TestMethodInfo + ) { + val testLaunchId = generateTestLaunchId() + testLaunchHolder.set(testLaunchId) + updateTestInfo(testLaunchId, testMethod) { + it.startedAt = System.currentTimeMillis() + } + addDrillHeaders(testLaunchId) + listeners.forEach { it.onTestStarted(testLaunchId, testMethod) } + logger.debug { "Test: $testMethod STARTED" } + } + + override fun recordTestFinishing( + testMethod: TestMethodInfo, + status: String + ) { + val testLaunchId = testLaunchHolder.get() + if (testLaunchId == null) { + logger.warn { "Test ${testMethod.className}::${testMethod.method} finished with result $status but no test launch id was found." } + return + } + testLaunchHolder.remove() + val testResult = mapToTestResult(status) + updateTestInfo(testLaunchId, testMethod) { + it.finishedAt = System.currentTimeMillis() + it.result = testResult + } + clearDrillHeaders() + listeners.forEach { it.onTestFinished(testLaunchId, testMethod, testResult) } + println("Test: $testMethod FINISHED") + logger.debug { "Test: $testMethod FINISHED. Result: $status" } + } + + override fun recordTestIgnoring( + testMethod: TestMethodInfo, + isSmartSkip: Boolean + ) { + val testLaunchId = testLaunchHolder.get() ?: generateTestLaunchId() + testLaunchHolder.remove() + val skipResult = if (isSmartSkip) TestResult.SMART_SKIPPED else TestResult.SKIPPED + updateTestInfo(testLaunchId, testMethod) { + it.startedAt = null + it.finishedAt = null + it.result = skipResult + } + clearDrillHeaders() + listeners.forEach { it.onTestIgnored(testLaunchId, testMethod) } + logger.debug { "Test: $testMethod FINISHED. Result: $skipResult" } + } + + override fun reset() { + testExecutionData.clear() + } + + override fun getFinishedTests(): List = testExecutionData + .filterValues { test -> test.result != TestResult.UNKNOWN } + .onEach { + testExecutionData.remove(it.key) + }.values.toList() + + private fun updateTestInfo( + testLaunchId: String, + testMethodInfo: TestMethodInfo, + updateTestExecutionInfo: (TestExecutionInfo) -> Unit, + ) { + testExecutionData.compute(testLaunchId) { _, value -> + val testExecutionInfo = value ?: TestExecutionInfo(testLaunchId, testMethodInfo) + if (testExecutionInfo.result == TestResult.UNKNOWN) { + updateTestExecutionInfo(testExecutionInfo) + } else { + logger.warn { "Test ${testMethodInfo.method} already finished with result ${testExecutionInfo.result}" } + } + testExecutionInfo + } + } + + private fun generateTestLaunchId() = UUID.randomUUID().toString() + + private fun addDrillHeaders(testLaunchId: String) { + val drillRequest = if (Configuration.parameters[ParameterDefinitions.TEST_TRACING_ENABLED]) { + DrillRequest( + drillSessionId = SessionController.getSessionId(), + headers = mapOf(TEST_ID_HEADER to (testLaunchId)) + ) + } else { + DrillRequest(drillSessionId = SessionController.getSessionId()) + } + requestHolder.store(drillRequest) + } + + private fun clearDrillHeaders() { + requestHolder.remove() + } + + private fun mapToTestResult(value: String): TestResult { + if (value == "SUCCESSFUL") return TestResult.PASSED + return TestResult.valueOf(value) + } +} \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/AbstractTestTransformerObject.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/AbstractTestTransformerObject.kt new file mode 100644 index 00000000..6a3ffbdb --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/AbstractTestTransformerObject.kt @@ -0,0 +1,51 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument + +import com.epam.drill.agent.configuration.Configuration +import com.epam.drill.agent.configuration.ParameterDefinitions.TEST_AGENT_ENABLED +import com.epam.drill.agent.instrument.AbstractTransformerObject +import com.epam.drill.agent.instrument.ClassPathProvider +import com.epam.drill.agent.instrument.RuntimeClassPathProvider +import javassist.ClassPool +import javassist.CtClass +import java.security.ProtectionDomain + +abstract class AbstractTestTransformerObject : AbstractTransformerObject(Configuration), + ClassPathProvider by RuntimeClassPathProvider { + override fun enabled() = super.enabled() && Configuration.parameters[TEST_AGENT_ENABLED] + + override fun transform(className: String, ctClass: CtClass) { + throw NotImplementedError("AbstractTestTransformerObject.transform is not implemented") + } + + override fun transform( + className: String, + ctClass: CtClass, + pool: ClassPool, + classLoader: ClassLoader?, + protectionDomain: ProtectionDomain? + ) { + instrument(ctClass, pool, classLoader, protectionDomain) + } + + abstract fun instrument( + ctClass: CtClass, + pool: ClassPool, + classLoader: ClassLoader?, + protectionDomain: ProtectionDomain?, + ) +} \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/RuntimeClassPathProvider.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/RuntimeClassPathProvider.kt new file mode 100644 index 00000000..85087676 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/RuntimeClassPathProvider.kt @@ -0,0 +1,26 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument + +import com.epam.drill.agent.configuration.DefaultParameterDefinitions.INSTALLATION_DIR +import com.epam.drill.agent.instrument.ClassPathProvider +import com.epam.drill.agent.configuration.Configuration + +object RuntimeClassPathProvider : ClassPathProvider { + + override fun getClassPath() = "${Configuration.parameters[INSTALLATION_DIR]}/drill-runtime.jar" + +} diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/TestSessionHeadersProcessor.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/TestSessionHeadersProcessor.kt new file mode 100644 index 00000000..de478380 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/TestSessionHeadersProcessor.kt @@ -0,0 +1,44 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument + +import com.epam.drill.agent.request.DrillRequestHolder +import com.epam.drill.agent.instrument.HeadersProcessor +import com.epam.drill.agent.test.SESSION_ID_HEADER +import com.epam.drill.agent.test.TEST_ID_HEADER + +object TestSessionHeadersProcessor : HeadersProcessor { + + override fun removeHeaders() = Unit + + override fun storeHeaders(headers: Map) = Unit + + override fun retrieveHeaders() = mutableMapOf().apply { + DrillRequestHolder.retrieve()?.let { + put(SESSION_ID_HEADER, it.drillSessionId) + putAll(it.headers) + } + } + + override fun hasHeaders() = retrieveHeaders().run { + this.isNotEmpty() && this.get(SESSION_ID_HEADER) != null && this.get(TEST_ID_HEADER) != null + } + + override fun isProcessRequests() = true + + override fun isProcessResponses() = false + +} diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/AbstractCucumber56Transformer.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/AbstractCucumber56Transformer.kt new file mode 100644 index 00000000..444d79bd --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/AbstractCucumber56Transformer.kt @@ -0,0 +1,68 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.cucumber + +import javassist.* + +abstract class AbstractCucumber56Transformer() : AbstractCucumberTransformer() { + override val Status = "io.cucumber.plugin.event.Status" + override val EventBus: String = "io.cucumber.core.eventbus.EventBus" + override val EventHandler = """io.cucumber.plugin.event.EventHandler""" + override val PickleStepDefinitionMatch: String = "io.cucumber.core.runner.PickleStepDefinitionMatch" + override val testPackage = "io.cucumber.plugin.event" + abstract val versionPattern: Regex + + /** + * From cucumber 5 TestStep class location doesn't change + */ + override fun permit(className: String, superName: String?, interfaces: Array): Boolean { + return className == "io/cucumber/core/runner/TestStep" + } + + override fun getFeaturePath(): String = """ + String[] paths = new java.io.File(".").toURI().relativize($1.getUri()).toString().split(":"); + int index = paths.length - 1; + String featurePath = paths[index]; + if (featurePath.startsWith("/")) { + featurePath = featurePath.replaceFirst("/", ""); + } + """.trimIndent() + + override fun getTestStatus(): String = """finishedTest.getResult().getStatus();""".trimIndent() + + override fun CtClass.implEventBusMethods() { + addMethod( + CtMethod.make( + """ + public java.time.Instant getInstant() { + return mainEventBus.getInstant(); + } + """.trimIndent(), + this + ) + ) + addMethod( + CtMethod.make( + """ + public java.util.UUID generateId() { + return mainEventBus.generateId(); + } + """.trimIndent(), + this + ) + ) + } +} diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/AbstractCucumberTransformer.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/AbstractCucumberTransformer.kt new file mode 100644 index 00000000..76e960b4 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/AbstractCucumberTransformer.kt @@ -0,0 +1,140 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.cucumber + +import com.epam.drill.agent.instrument.InstrumentationParameterDefinitions.INSTRUMENTATION_CUCUMBER_ENABLED +import com.epam.drill.agent.test.execution.TestController +import com.epam.drill.agent.test.instrument.AbstractTestTransformerObject +import javassist.* +import mu.KotlinLogging +import java.security.* + +abstract class AbstractCucumberTransformer() : AbstractTestTransformerObject() { + val engineSegment = "cucumber" + val EventBusProxy = "EventBusProxy" + + override val logger = KotlinLogging.logger {} + abstract val Status: String + abstract val EventBus: String + abstract val EventHandler: String + abstract val Event: String + abstract val PickleStepDefinitionMatch: String + abstract val testPackage: String + + override fun enabled() = super.enabled() && agentConfiguration.parameters[INSTRUMENTATION_CUCUMBER_ENABLED] + + override fun instrument( + ctClass: CtClass, + pool: ClassPool, + classLoader: ClassLoader?, + protectionDomain: ProtectionDomain?, + ) { + val cc: CtClass = pool.makeClass(EventBusProxy) + cc.interfaces = arrayOf(pool.get(EventBus)) + cc.addField(CtField.make("$EventBus mainEventBus = null;", cc)) + cc.addField(CtField.make("String featurePath = \"\";", cc)) + cc.addConstructor( + CtNewConstructor.make( + """ + public $EventBusProxy($EventBus mainEventBus, String featurePath) { + this.mainEventBus = mainEventBus; + this.featurePath = featurePath; + } + """.trimMargin(), + cc + ) + ) + cc.implEventBusMethods() + + cc.addMethod( + CtMethod.make( + """ + public void send($Event event) { + mainEventBus.send(event); + if (event instanceof $testPackage.TestStepStarted) { + ${TestController::class.java.name}.INSTANCE.${TestController::testStarted.name}("$engineSegment", featurePath, (($testPackage.TestStepStarted) event).getTestCase().getName()); + } else if (event instanceof $testPackage.TestStepFinished) { + $testPackage.TestStepFinished finishedTest = ($testPackage.TestStepFinished) event; + $Status status = ${getTestStatus()} + if (status != $Status.PASSED) { + status = $Status.FAILED; + } + ${TestController::class.java.name}.INSTANCE.${TestController::testFinished.name}("$engineSegment", featurePath, finishedTest.getTestCase().getName(), status.name()); + } + } + """.trimIndent(), + cc + ) + ) + cc.addMethod( + CtMethod.make( + """ + public void sendAll(Iterable queue) { + mainEventBus.sendAll(queue); + } + """.trimIndent(), + cc + ) + ) + cc.addMethod( + CtMethod.make( + """ + public void removeHandlerFor(Class aClass, $EventHandler eventHandler) { + mainEventBus.removeHandlerFor(aClass, eventHandler); + } + """.trimIndent(), + cc + ) + ) + + cc.addMethod( + CtMethod.make( + """ + public void registerHandlerFor(Class aClass, $EventHandler eventHandler) { + mainEventBus.registerHandlerFor(aClass, eventHandler); + } + """.trimIndent(), + cc + ) + ) + cc.toClass(classLoader, protectionDomain) + + /** + * {@link PickleStepDefinitionMatch} is represent a step of scenario. + * Check for PickleStepDefinitionMatch is needed to determine what we are currently performing, + * a step from a scenario or before or after action. + * Instead of the class name, we use the path to the feature file. + * If the file is in the same repository as the tests, then we take the relative path, + * otherwise we take the absolute path without specifying the disk name + */ + ctClass.getDeclaredMethod("run").insertBefore( + """ + try { + if (stepDefinitionMatch instanceof $PickleStepDefinitionMatch) { + ${getFeaturePath()} + $2 = new $EventBusProxy($2, featurePath); + } + } catch (Throwable ignored) {} + """.trimIndent() + ) + } + + abstract fun getFeaturePath(): String + + abstract fun getTestStatus(): String + + abstract fun CtClass.implEventBusMethods() +} diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber4Transformer.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber4Transformer.kt new file mode 100644 index 00000000..eaac09c4 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber4Transformer.kt @@ -0,0 +1,66 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.cucumber + +import com.epam.drill.agent.instrument.TransformerObject +import javassist.* + +actual object Cucumber4Transformer : TransformerObject, AbstractCucumberTransformer() { + override val testPackage = "cucumber.api.event" + override val Status = "cucumber.api.Result.Type" + override val EventBus = "cucumber.runner.EventBus" + override val EventHandler: String = "cucumber.api.event.EventHandler" + override val Event = "cucumber.api.event.Event" + override val PickleStepDefinitionMatch = "cucumber.runner.PickleStepDefinitionMatch" + + override fun permit(className: String, superName: String?, interfaces: Array): Boolean { + return className == /*4.x.x*/"cucumber/runner/TestStep" + } + + override fun getFeaturePath(): String = """ + String[] paths = new java.io.File(".").toURI().resolve($1.getUri()).toString().split(":"); + int index = paths.length - 1; + String featurePath = paths[index]; + if (featurePath.startsWith("/")) { + featurePath = featurePath.replaceFirst("/", ""); + } + """.trimIndent() + + override fun getTestStatus(): String = """finishedTest.result.getStatus();""".trimIndent() + + override fun CtClass.implEventBusMethods() { + addMethod( + CtMethod.make( + """ + public Long getTime() { + return mainEventBus.getTime(); + } + """.trimIndent(), + this + ) + ) + addMethod( + CtMethod.make( + """ + public Long getTimeMillis() { + return mainEventBus.getTimeMillis(); + } + """.trimIndent(), + this + ) + ) + } +} diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber5Transformer.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber5Transformer.kt new file mode 100644 index 00000000..4b4c1ccc --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber5Transformer.kt @@ -0,0 +1,37 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.cucumber + +import com.epam.drill.agent.instrument.TransformerObject +import java.security.ProtectionDomain +import javassist.ClassPool +import javassist.CtClass + +actual object Cucumber5Transformer : TransformerObject, AbstractCucumber56Transformer() { + override val versionPattern: Regex = "5\\.[0-9]+\\.[0-9]+".toRegex() + override val Event: String = "io.cucumber.plugin.event.Event" + + override fun instrument( + ctClass: CtClass, + pool: ClassPool, + classLoader: ClassLoader?, + protectionDomain: ProtectionDomain?, + ) { + if ("${ctClass.url}".contains(versionPattern)) { + super.instrument(ctClass, pool, classLoader, protectionDomain) + } + } +} diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber6Transformer.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber6Transformer.kt new file mode 100644 index 00000000..2e592bae --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber6Transformer.kt @@ -0,0 +1,37 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.cucumber + +import com.epam.drill.agent.instrument.TransformerObject +import java.security.ProtectionDomain +import javassist.ClassPool +import javassist.CtClass + +actual object Cucumber6Transformer : TransformerObject, AbstractCucumber56Transformer() { + override val versionPattern: Regex = "6\\.[0-9]+\\.[0-9]+".toRegex() + override val Event: String = "java.lang.Object" + + override fun instrument( + ctClass: CtClass, + pool: ClassPool, + classLoader: ClassLoader?, + protectionDomain: ProtectionDomain?, + ) { + if ("${ctClass.url}".contains(versionPattern)) { + super.instrument(ctClass, pool, classLoader, protectionDomain) + } + } +} diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/jmeter/JMeterTransformer.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/jmeter/JMeterTransformer.kt new file mode 100644 index 00000000..b561d3a3 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/jmeter/JMeterTransformer.kt @@ -0,0 +1,54 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.jmeter + +import com.epam.drill.agent.instrument.InstrumentationParameterDefinitions.INSTRUMENTATION_JMETER_ENABLED +import com.epam.drill.agent.instrument.TransformerObject +import com.epam.drill.agent.test.instrument.AbstractTestTransformerObject +import javassist.* +import mu.KotlinLogging +import java.security.* + +actual object JMeterTransformer : TransformerObject, AbstractTestTransformerObject() { + override val logger = KotlinLogging.logger {} + + override fun enabled() = super.enabled() && agentConfiguration.parameters[INSTRUMENTATION_JMETER_ENABLED] + + override fun permit(className: String, superName: String?, interfaces: Array): Boolean { + return className == "org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl" + } + + override fun instrument( + ctClass: CtClass, + pool: ClassPool, + classLoader: ClassLoader?, + protectionDomain: ProtectionDomain? + ) { + val setupRequestMethod = ctClass.getMethod( + "setupRequest", + "(Ljava/net/URL;Lorg/apache/http/client/methods/HttpRequestBase;" + + "Lorg/apache/jmeter/protocol/http/sampler/HTTPSampleResult;)V" + ) + //TODO memorizeTestName is not exist + setupRequestMethod.insertBefore( + """ + String drillTestName = $3.getSampleLabel(); + AgentClassTransformer.memorizeTestName(drillTestName); + + """.trimIndent() + ) + } +} diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/junit/AbstractJUnitTransformer.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/junit/AbstractJUnitTransformer.kt new file mode 100644 index 00000000..d6bfaac9 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/junit/AbstractJUnitTransformer.kt @@ -0,0 +1,23 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.junit + +import com.epam.drill.agent.instrument.InstrumentationParameterDefinitions.INSTRUMENTATION_JUNIT_ENABLED +import com.epam.drill.agent.test.instrument.AbstractTestTransformerObject + +abstract class AbstractJUnitTransformer(): AbstractTestTransformerObject() { + override fun enabled() = super.enabled() && agentConfiguration.parameters[INSTRUMENTATION_JUNIT_ENABLED] +} \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit4PrioritizingTransformer.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit4PrioritizingTransformer.kt new file mode 100644 index 00000000..f052f802 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit4PrioritizingTransformer.kt @@ -0,0 +1,95 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.junit + +import com.epam.drill.agent.configuration.ParameterDefinitions +import com.epam.drill.agent.instrument.TransformerObject +import com.epam.drill.agent.test.prioritization.RecommendedTests +import javassist.* +import mu.KotlinLogging +import java.security.ProtectionDomain + +private const val Filter = "org.junit.runner.manipulation.Filter" +private const val Description = "org.junit.runner.Description" + +actual object JUnit4PrioritizingTransformer : TransformerObject, AbstractJUnitTransformer() { + override val logger = KotlinLogging.logger {} + private val engineSegment = "junit" + private val DrillJUnit4Filter = "${this.javaClass.`package`.name}.gen.DrillJUnit4Filter" + + override fun enabled(): Boolean = super.enabled() && agentConfiguration.parameters[ParameterDefinitions.RECOMMENDED_TESTS_ENABLED] + + override fun permit(className: String, superName: String?, interfaces: Array): Boolean { + return className == "org/junit/runners/JUnit4" + } + + override fun instrument( + ctClass: CtClass, + pool: ClassPool, + classLoader: ClassLoader?, + protectionDomain: ProtectionDomain?, + ) { + createRecommendedTestsFilterClass(pool, classLoader, protectionDomain) + instrumentConstructor(ctClass) + } + + private fun instrumentConstructor(ctClass: CtClass) { + ctClass.constructors.forEach { constructor -> + constructor.insertAfter( + """ + $DrillJUnit4Filter drillFilter = new $DrillJUnit4Filter(); + filter(drillFilter); + """.trimIndent() + ) + } + } + + private fun createRecommendedTestsFilterClass( + pool: ClassPool, + classLoader: ClassLoader?, + protectionDomain: ProtectionDomain?, + ): CtClass { + val cc = pool.makeClass(DrillJUnit4Filter, pool.get(Filter)) + cc.addMethod( + CtMethod.make( + """ + public boolean shouldRun($Description description) { + if (!description.isTest()) return true; + java.lang.String className = description.getClassName(); + if (className == null) return true; + java.lang.String methodName = description.getMethodName(); + if (methodName == null) return true; + boolean shouldSkip = ${RecommendedTests::class.java.name}.INSTANCE.${RecommendedTests::shouldSkip.name}("$engineSegment", className, methodName, null); + return !shouldSkip; + } + """.trimIndent(), + cc + ) + ) + cc.addMethod( + CtMethod.make( + """ + public java.lang.String describe() { + return "skip tests by Drill4J"; + } + """.trimIndent(), + cc + ) + ) + cc.toClass(classLoader, protectionDomain) + return cc + } +} \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit4Transformer.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit4Transformer.kt new file mode 100644 index 00000000..b352ac6c --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit4Transformer.kt @@ -0,0 +1,162 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.junit + +import com.epam.drill.agent.instrument.TransformerObject +import com.epam.drill.agent.test.execution.TestController +import com.epam.drill.agent.test.execution.TestResult +import javassist.ClassPool +import javassist.CtClass +import javassist.CtField +import javassist.CtMethod +import javassist.CtNewConstructor +import mu.KotlinLogging +import java.security.ProtectionDomain + +actual object JUnit4Transformer: + TransformerObject, + AbstractJUnitTransformer() { + override val logger = KotlinLogging.logger {} + const val engineSegment = "junit" + + override fun permit( + className: String, + superName: String?, + interfaces: Array + ): Boolean { + return className == "org/junit/runner/notification/RunNotifier" + } + + override fun instrument( + ctClass: CtClass, + pool: ClassPool, + classLoader: ClassLoader?, + protectionDomain: ProtectionDomain? + ) { + val cc: CtClass = pool.makeClass("MyList") + cc.superclass = pool.get("org.junit.runner.notification.RunListener") + cc.addField(CtField.make("org.junit.runner.notification.RunListener mainRunner = null;", cc)) + cc.addConstructor( + CtNewConstructor.make( + """ + public MyList(org.junit.runner.notification.RunListener mainRunner) { + this.mainRunner = mainRunner; + } + """.trimMargin(), cc + ) + ) + val dp = """description""" + cc.addMethod( + CtMethod.make( + """ + public void testRunStarted(org.junit.runner.Description $dp) throws Exception { + this.mainRunner.testRunStarted($dp); + } + """.trimIndent(), + cc + ) + ) + + cc.addMethod( + CtMethod.make( + """ + public void testStarted(org.junit.runner.Description $dp) throws Exception { + this.mainRunner.testStarted($dp); + ${TestController::class.java.name}.INSTANCE.${TestController::testStarted.name}("$engineSegment", $dp.getClassName(), $dp.getMethodName()); + } + """.trimIndent(), + cc + ) + ) + + + cc.addMethod( + CtMethod.make( + """ + public void testFinished(org.junit.runner.Description $dp) throws Exception { + this.mainRunner.testFinished(description); + ${TestController::class.java.name}.INSTANCE.${TestController::testFinished.name}("$engineSegment", $dp.getClassName(), $dp.getMethodName(), "${TestResult.PASSED.name}"); + } + """.trimIndent(), + cc + ) + ) + + cc.addMethod( + CtMethod.make( + """ + public void testRunFinished(org.junit.runner.Result result) throws Exception { + this.mainRunner.testRunFinished(result); + } + """.trimIndent(), + cc + ) + ) + + + val failureParamName = """failure""" + val desct = """$failureParamName.getDescription()""" + cc.addMethod( + CtMethod.make( + """ + public void testFailure(org.junit.runner.notification.Failure $failureParamName) throws Exception { + this.mainRunner.testFailure($failureParamName); + ${TestController::class.java.name}.INSTANCE.${TestController::testFinished.name}("$engineSegment", $desct.getClassName(), $desct.getMethodName(), "${TestResult.FAILED.name}"); + } + """.trimIndent(), + cc + ) + ) + + + cc.addMethod( + CtMethod.make( + """ + public void testAssumptionFailure(org.junit.runner.notification.Failure $failureParamName) { + this.mainRunner.testAssumptionFailure(failure); + } + """.trimIndent(), + cc + ) + ) + + + + cc.addMethod( + CtMethod.make( + """ + public void testIgnored(org.junit.runner.Description $dp) throws Exception { + this.mainRunner.testIgnored($dp); + ${TestController::class.java.name}.INSTANCE.${TestController::testIgnored.name}("$engineSegment", $dp.getClassName(), $dp.getMethodName()); + } + """.trimIndent(), + cc + ) + ) + + cc.toClass(classLoader, protectionDomain) + ctClass.getDeclaredMethod("addListener").insertBefore( + """ + $1= new MyList($1); + """.trimIndent() + ) + ctClass.getDeclaredMethod("addFirstListener").insertBefore( + """ + $1= new MyList($1); + """.trimIndent() + ) + } +} \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit5Transformer.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit5Transformer.kt new file mode 100644 index 00000000..b1f83fce --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit5Transformer.kt @@ -0,0 +1,152 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.junit + +import com.epam.drill.agent.instrument.TransformerObject +import com.epam.drill.agent.test.execution.TestController +import com.epam.drill.agent.test.execution.TestMethodInfo +import javassist.* +import mu.KotlinLogging +import java.security.* + +actual object JUnit5Transformer: TransformerObject, AbstractJUnitTransformer() { + override val logger = KotlinLogging.logger {} + + override fun permit(className: String, superName: String?, interfaces: Array): Boolean { + return className == "org/junit/platform/engine/support/hierarchical/NodeTestTaskContext" + } + + override fun instrument( + ctClass: CtClass, + pool: ClassPool, + classLoader: ClassLoader?, + protectionDomain: ProtectionDomain?, + ) { + val cc: CtClass = pool.makeClass("MyList") + cc.interfaces = arrayOf(pool.get("org.junit.platform.engine.EngineExecutionListener")) + cc.addField(CtField.make("org.junit.platform.engine.EngineExecutionListener mainRunner = null;", cc)) + cc.addConstructor( + CtNewConstructor.make( + """ + public MyList(org.junit.platform.engine.EngineExecutionListener mainRunner) { + this.mainRunner = mainRunner; + } + """.trimMargin(), cc + ) + ) + cc.addMethod( + CtMethod.make( + """ + public void dynamicTestRegistered(org.junit.platform.engine.TestDescriptor testDescriptor) { + mainRunner.dynamicTestRegistered(testDescriptor); + } + """.trimIndent(), + cc + ) + ) + cc.addMethod( + CtMethod.make( + """ + public void executionSkipped(org.junit.platform.engine.TestDescriptor testDescriptor, String reason) { + mainRunner.executionSkipped(testDescriptor, reason); + if (!testDescriptor.isContainer()) { + ${getMetadata("testDescriptor")} + ${getTags("testDescriptor")} + ${TestMethodInfo::class.java.name} methodInfo = ${this::class.java.name}.INSTANCE.${this::convertToMethodInfo.name}(testMetadata, testTags); + if (methodInfo != null) { + ${TestController::class.java.name}.INSTANCE.${TestController::recordTestIgnoring.name}(methodInfo, false); + } + } + } + """.trimIndent(), + cc + ) + ) + cc.addMethod( + CtMethod.make( + """ + public void executionStarted(org.junit.platform.engine.TestDescriptor testDescriptor) { + mainRunner.executionStarted(testDescriptor); + if (!testDescriptor.isContainer()) { + ${getMetadata("testDescriptor")} + ${getTags("testDescriptor")} + ${TestMethodInfo::class.java.name} methodInfo = ${this::class.java.name}.INSTANCE.${this::convertToMethodInfo.name}(testMetadata, testTags); + if (methodInfo != null) { + ${TestController::class.java.name}.INSTANCE.${TestController::recordTestStarting.name}(methodInfo); + } + } + } + """.trimIndent(), + cc + ) + ) + cc.addMethod( + CtMethod.make( + """ + public void executionFinished(org.junit.platform.engine.TestDescriptor testDescriptor, org.junit.platform.engine.TestExecutionResult testExecutionResult) { + mainRunner.executionFinished(testDescriptor, testExecutionResult); + if (!testDescriptor.isContainer()) { + ${getMetadata("testDescriptor")} + ${getTags("testDescriptor")} + ${TestMethodInfo::class.java.name} methodInfo = ${this::class.java.name}.INSTANCE.${this::convertToMethodInfo.name}(testMetadata, testTags); + if (methodInfo != null) { + ${TestController::class.java.name}.INSTANCE.${TestController::recordTestFinishing.name}(methodInfo, testExecutionResult.getStatus().name()); + } + } + } + """.trimIndent(), + cc + ) + ) + cc.addMethod( + CtMethod.make( + """ + public void reportingEntryPublished(org.junit.platform.engine.TestDescriptor testDescriptor, org.junit.platform.engine.reporting.ReportEntry entry) { + mainRunner.reportingEntryPublished(testDescriptor, entry); + } + """.trimIndent(), + cc + ) + ) + cc.toClass(classLoader, protectionDomain) + + ctClass.constructors.first().insertBefore( + """ + $1 = new MyList($1); + """.trimIndent() + ) + } + + @Suppress("MemberVisibilityCanBePrivate") + fun convertToMethodInfo( + testMetadata: Map, + testTags: List + ): TestMethodInfo? { + return TestMethodInfo( + engine = testMetadata["engine"] ?: "junit", + className = testMetadata["class"] ?: return null, + method = testMetadata["method"]?.substringBefore("(") ?: return null, + methodParams = testMetadata["method"]?.getMethodParams() ?: "()", + metadata = testMetadata, + tags = testTags + ) + } + + private fun String.getMethodParams(): String { + val params = this.substringAfter("(").substringBefore(")") + return if (params.isEmpty()) "()" else "($params)" + } +} diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnitClassNames.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnitClassNames.kt new file mode 100644 index 00000000..983d3ce4 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnitClassNames.kt @@ -0,0 +1,43 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.junit + +const val PostDiscoveryFilter = "org.junit.platform.launcher.PostDiscoveryFilter" +const val FilterResult = "org.junit.platform.engine.FilterResult" +const val TestDescriptor = "org.junit.platform.engine.TestDescriptor" +const val Segment = "org.junit.platform.engine.UniqueId.Segment" +const val LauncherDiscoveryRequest = "org.junit.platform.launcher.LauncherDiscoveryRequest" +const val ConfigurationParameters = "org.junit.platform.engine.ConfigurationParameters" +const val TestTag = "org.junit.platform.engine.TestTag" + +fun getMetadata(descriptor: String) = """ + java.util.Map testMetadata = new java.util.HashMap(); + for (int i = 0; i < $descriptor.getUniqueId().getSegments().size(); i++) { + java.lang.String key = (($Segment)$descriptor.getUniqueId().getSegments().get(i)).getType(); + java.lang.String value = (($Segment)$descriptor.getUniqueId().getSegments().get(i)).getValue(); + testMetadata.put(key, value); + } + """.trimIndent() + +fun getTags(descriptor: String) = """ + java.util.List testTags = new java.util.ArrayList(); + java.util.Set tags = $descriptor.getTags(); + java.util.Iterator iterator = tags.iterator(); + while (iterator.hasNext()) { + $TestTag tag = ($TestTag) iterator.next(); + testTags.add(tag.getName()); + } + """.trimIndent() \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnitPlatformPrioritizingTransformer.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnitPlatformPrioritizingTransformer.kt new file mode 100644 index 00000000..aee43dee --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnitPlatformPrioritizingTransformer.kt @@ -0,0 +1,201 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.junit + +import com.epam.drill.agent.configuration.Configuration +import com.epam.drill.agent.configuration.ParameterDefinitions +import com.epam.drill.agent.instrument.TransformerObject +import com.epam.drill.agent.test.execution.TestMethodInfo +import com.epam.drill.agent.test.prioritization.RecommendedTests +import javassist.* +import mu.KotlinLogging +import java.security.ProtectionDomain + +actual object JUnitPlatformPrioritizingTransformer : TransformerObject, AbstractJUnitTransformer() { + + override val logger = KotlinLogging.logger {} + private val DrillJUnit5Filter = "${this.javaClass.`package`.name}.gen.DrillJUnit5Filter" + private val LauncherDiscoveryRequestAdapter = "${this.javaClass.`package`.name}.gen.LauncherDiscoveryRequestAdapter" + + override fun enabled(): Boolean = Configuration.parameters[ParameterDefinitions.RECOMMENDED_TESTS_ENABLED] + + override fun permit(className: String, superName: String?, interfaces: Array): Boolean { + return className == "org/junit/platform/launcher/core/DefaultLauncher" + } + + override fun instrument( + ctClass: CtClass, + pool: ClassPool, + classLoader: ClassLoader?, + protectionDomain: ProtectionDomain?, + ) { + createRecommendedTestsFilterClass(pool, classLoader, protectionDomain) + createLauncherDiscoveryRequestAdapterClass(pool, classLoader, protectionDomain) + instrumentDiscoverMethod(ctClass) + instrumentExecuteMethod(ctClass) + } + + private fun instrumentDiscoverMethod(ctClass: CtClass) { + ctClass.getMethod( + "discover", + "(Lorg/junit/platform/launcher/LauncherDiscoveryRequest;)Lorg/junit/platform/launcher/TestPlan;" + ).insertBefore( + """ + $1 = new $LauncherDiscoveryRequestAdapter($1, new $DrillJUnit5Filter()); + """.trimIndent() + ) + } + + private fun instrumentExecuteMethod(ctClass: CtClass) { + ctClass.getMethod( + "execute", + "(Lorg/junit/platform/launcher/LauncherDiscoveryRequest;[Lorg/junit/platform/launcher/TestExecutionListener;)V") + .insertBefore( + """ + $1 = new $LauncherDiscoveryRequestAdapter($1, new $DrillJUnit5Filter()); + """.trimIndent() + ) + } + + private fun createRecommendedTestsFilterClass( + pool: ClassPool, + classLoader: ClassLoader?, + protectionDomain: ProtectionDomain?, + ): CtClass { + val cc: CtClass = pool.makeClass(DrillJUnit5Filter) + cc.interfaces = arrayOf(pool.get(PostDiscoveryFilter)) + cc.addMethod( + CtMethod.make( + """ + public $FilterResult apply(java.lang.Object object) { + $TestDescriptor descriptor = ($TestDescriptor)object; + if (descriptor.isContainer()) + return $FilterResult.included(""); + + ${getMetadata("descriptor")} + ${getTags("descriptor")} + + ${TestMethodInfo::class.java.name} methodInfo = ${this::class.java.name}.INSTANCE.${this::convertToMethodInfo.name}(testMetadata, testTags, descriptor.getDisplayName()); + boolean shouldSkip = methodInfo != null && ${RecommendedTests::class.java.name}.INSTANCE.${RecommendedTests::shouldSkipByTestMethod.name}(methodInfo); + if (shouldSkip) { + return $FilterResult.excluded("skipped by Drill4J"); + } else { + return $FilterResult.included("recommended by Drill4J"); + } + } + """.trimIndent(), + cc + ) + ) + cc.toClass(classLoader, protectionDomain) + return cc + } + + private fun createLauncherDiscoveryRequestAdapterClass( + pool: ClassPool, + classLoader: ClassLoader?, + protectionDomain: ProtectionDomain?, + ): CtClass { + val cc: CtClass = pool.makeClass(LauncherDiscoveryRequestAdapter) + cc.interfaces = arrayOf(pool.get(LauncherDiscoveryRequest)) + cc.addField(CtField.make("$LauncherDiscoveryRequest delegate = null;", cc)) + cc.addField(CtField.make("$PostDiscoveryFilter additionalFilter = null;", cc)) + cc.addConstructor( + CtNewConstructor.make( + """ + public LauncherDiscoveryRequestAdapter($LauncherDiscoveryRequest delegate, $PostDiscoveryFilter additionalFilter) { + this.delegate = delegate; + this.additionalFilter = additionalFilter; + } + """.trimIndent(), + cc + ) + ) + cc.addMethod( + CtMethod.make( + """ + public java.util.List getEngineFilters() { + return delegate.getEngineFilters(); + } + """.trimIndent(), + cc + ) + ) + cc.addMethod( + CtMethod.make( + """ + public java.util.List getPostDiscoveryFilters() { + java.util.ArrayList modifiedList = new java.util.ArrayList(delegate.getPostDiscoveryFilters()); + modifiedList.add(additionalFilter); + return modifiedList; + } + """.trimIndent(), + cc + ) + ) + cc.addMethod( + CtMethod.make( + """ + public java.util.List getSelectorsByType(java.lang.Class selectorType) { + return delegate.getSelectorsByType(selectorType); + } + """.trimIndent(), + cc + ) + ) + cc.addMethod( + CtMethod.make( + """ + public java.util.List getFiltersByType(java.lang.Class filterType) { + return delegate.getFiltersByType(filterType); + } + """.trimIndent(), + cc + ) + ) + cc.addMethod( + CtMethod.make( + """ + public $ConfigurationParameters getConfigurationParameters() { + return delegate.getConfigurationParameters(); + } + """.trimIndent(), + cc + ) + ) + cc.toClass(classLoader, protectionDomain) + return cc + } + + @Suppress("MemberVisibilityCanBePrivate") + fun convertToMethodInfo(testMetadata: Map, + testTags: List, + displayName: String?): TestMethodInfo? { + val testPath = testMetadata["class"] ?: testMetadata["feature"] ?: testMetadata["suite"] + val testName = testMetadata["method"]?.substringBefore("(") ?: displayName + if (testPath == null || testName == null) { + logger.error { "Failed to convert test metadata to TestDetails: $testMetadata" } + return null + } + return TestMethodInfo( + engine = testMetadata["engine"] ?: "junit", + className = testPath, + method = testName, + tags = testTags, + metadata = testMetadata + ) + } +} \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/selenium/ChromeDevTool.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/selenium/ChromeDevTool.kt new file mode 100644 index 00000000..953c6528 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/selenium/ChromeDevTool.kt @@ -0,0 +1,354 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.selenium + +import com.epam.drill.agent.test.* +import com.epam.drill.agent.test.serialization.* +import com.epam.drill.agent.test.session.* +import com.epam.drill.agent.common.transport.AgentMessage +import com.epam.drill.agent.common.transport.ResponseStatus +import com.epam.drill.agent.configuration.Configuration +import com.epam.drill.agent.configuration.ParameterDefinitions +import com.epam.drill.agent.test.instrument.TestSessionHeadersProcessor +import com.epam.drill.agent.test.devtools.DevToolsMessageSender +import com.epam.drill.agent.test.serialization.json +import kotlinx.serialization.* +import kotlinx.serialization.json.* +import kotlin.time.DurationUnit +import kotlin.time.measureTimedValue +import java.net.* +import java.util.* +import mu.KotlinLogging +import kotlin.reflect.KClass + +private const val DEBUGGER_ADDRESS = "debuggerAddress" +private const val DEV_TOOL_DEBUGGER_URL = "webSocketDebuggerUrl" +private val JAVA_TOGGLES = listOf("Network") +private val JS_TOGGLES = listOf("Debugger", "Profiler") + .takeIf { Configuration.parameters[ParameterDefinitions.WITH_JS_COVERAGE] } + ?: emptyList() +private val REPLACE_LOCALHOST = Configuration.parameters[ParameterDefinitions.DEVTOOLS_REPLACE_LOCALHOST] + +/** + * Works with local or Selenoid DevTools by websocket + */ +class ChromeDevTool( + private val capabilities: Map<*, *>?, + private val remoteHost: String? +) { + private val logger = KotlinLogging.logger {} + private val launchType = Configuration.parameters[ParameterDefinitions.LAUNCH_TYPE] + private var isClosed = false + + private lateinit var targetUrl: String + private lateinit var targetId: String + private var sessionId: SessionId = SessionId() + private var headersAdded: Boolean = false + + /** + * connect to remote Selenoid or local webDriver + */ + fun connect(browserSessionId: String?, currentUrl: String) = runCatching { + logger.debug { "starting connectToDevTools with cap='$capabilities' sessionId='$sessionId' remote='$remoteHost'..." } + retrieveDevToolAddress(capabilities ?: emptyMap(), browserSessionId, remoteHost).let { + trackTime("connect to devtools") { + connect(it, currentUrl) + } + } + /** + * Add this to thread local only if successfully connected + */ + }.onFailure { logger.warn(it) { "UI coverage will be lost. Reason: " } }.getOrNull() + + fun addHeaders(headers: Map<*, *>) { + @Suppress("UNCHECKED_CAST") + val casted = headers as Map + try { + logger.trace { "try to add headers: $headers" } + val success = setHeaders(casted) + logger.debug { "Chrome Tool activated: ${sessionId.sessionId.isNotBlank()}. Headers: $headers" } + if (!success) throw RuntimeException("Can't add headers: $headers") + } catch (ex: Exception) { + logger.debug { "exception $ex; try to resend" } + Thread.sleep(2000) + setHeaders(casted) + } + } + + fun switchSession(url: String) { + val targetId = retrieveTargetId(url) + logger.trace { "Reconnect to target: $targetId, sessionId: ${sessionId.sessionId}, url $url" } + targetId?.takeIf { it != this.targetId }?.let { attachToTarget(it) }?.let { + this.targetId = targetId + sessionId = it + enableToggles() + startCollectJsCoverage() + } + } + + fun isHeadersAdded(): Boolean = this.headersAdded + + private fun startCollectJsCoverage() = + Configuration.parameters[ParameterDefinitions.WITH_JS_COVERAGE].takeIf(true::equals)?.let { + disableCache() && startPreciseCoverage() && enableScriptParsed() + }?.also { success -> + if (!success) logger.warn { "JS coverage may be lost" } + } + + private fun startPreciseCoverage() = mapOf("detailed" to true, "callCount" to false).let { params -> + executeCommand( + "Profiler.startPreciseCoverage", + DevToolsRequest(targetUrl, sessionId.sessionId, params.toOutput()) + ).success + } + + private fun disableCache() = executeCommand( + "Network.setCacheDisabled", + DevToolsRequest(targetUrl, sessionId.sessionId, mapOf("cacheDisabled" to true).toOutput()) + ).success + + private fun enableScriptParsed() = DevToolsMessageSender.send( + "POST", + "/event/Debugger.scriptParsed", + DevToolsRequest(targetUrl, sessionId.sessionId) + ).success + + fun takePreciseCoverage(): String = executeCommand( + "Profiler.takePreciseCoverage", + DevToolsRequest(targetUrl, sessionId.sessionId) + ).takeIf(ResponseStatus::success)?.content ?: "" + + fun scriptParsed(): String = DevToolsMessageSender.send( + "POST", + "/event/Debugger.scriptParsed/get-data", + DevToolsRequest(targetUrl, sessionId.sessionId) + ).takeIf(ResponseStatus::success)?.content ?: "" + + fun close() { + if (!isClosed) { + disableToggles() + stopCollectJsCoverage() + DevToolsMessageSender.send( + "DELETE", + "/connection", + DevToolsRequest(targetUrl) + ) + } + isClosed = true + } + + private fun stopCollectJsCoverage() = + Configuration.parameters[ParameterDefinitions.WITH_JS_COVERAGE].takeIf(true::equals)?.let { + DevToolsMessageSender.send( + "DELETE", + "/event/Debugger.scriptParsed", + DevToolsRequest(targetUrl, sessionId.sessionId) + ) + } + + // todo is it necessary to disable toggles when browser exit? + private fun disableToggles() = (JAVA_TOGGLES + JS_TOGGLES).map { + executeCommand( + "$it.disable", + DevToolsRequest(targetUrl, sessionId.sessionId) + ).success + }.all { it } + + private fun enableToggles() = (JAVA_TOGGLES + JS_TOGGLES).map { + executeCommand( + "$it.enable", + DevToolsRequest(targetUrl, sessionId.sessionId) + ).success + }.all { it }.also { + if (!it) logger.warn { "Toggles wasn't enable" } else logger.info { "Toggles enabled" } + } + + private fun retrieveDevToolAddress( + capabilities: Map<*, *>, + sessionId: String?, + remoteHost: String? + ): String = when (launchType) { + + // selenoid provides no access to /json/version, but allows to connect to debugger directly + // see https://aerokube.com/selenoid/latest/#_accessing_browser_developer_tools + "selenoid" -> { + if (sessionId.isNullOrBlank()) + throw RuntimeException("Can't connect to debugger directly, because 'sessionId' is null") + if (remoteHost.isNullOrBlank()) + throw RuntimeException("Can't connect to debugger directly, because 'remoteHost' is null") + "ws://$remoteHost/devtools/$sessionId" + } + + else -> capabilities.run { + val debuggerURL = get(DEBUGGER_ADDRESS)?.toString() + + if (debuggerURL.isNullOrBlank()) { + error("Can't get debugger address by field name $DEBUGGER_ADDRESS from capabilities: $capabilities}") + } + + return DevToolsMessageSender.send("http://$debuggerURL", "GET", "/json/version", "") + .onError { error -> + error("Can't get debugger address from http://$debuggerURL/json/version: $error") + } + .onSuccess { content -> + logger.trace { "/json/version: $content" } + + }.content?.let { content -> + val chromeInfo = Json.parseToJsonElement(content) as JsonObject + chromeInfo[DEV_TOOL_DEBUGGER_URL]?.jsonPrimitive?.contentOrNull + } + ?: error("Can't get debugger address from '$DEV_TOOL_DEBUGGER_URL' field") + } + } + + + private fun connect(devToolAddress: String, currentUrl: String) { + if (REPLACE_LOCALHOST.isNotBlank()) { + targetUrl = devToolAddress.replace("localhost", REPLACE_LOCALHOST) + } else { + targetUrl = devToolAddress + } + val success: Boolean = connectToDevTools().takeIf { it }?.also { + val targetId = retrieveTargetId(currentUrl) + logger.info { "Retrieved target for url $currentUrl: $targetId" } + targetId?.let { attachToTarget(it) }?.also { + this.targetId = targetId + sessionId = it + logger.debug { "DevTools session created: $sessionId" } + enableToggles() + startCollectJsCoverage() + } + } ?: false + if (success) { + DevToolStorage.set(this) + } else throw RuntimeException("Can't connect to $targetUrl") + } + + private fun connectToDevTools(): Boolean { + logger.debug { "DevTools URL: $targetUrl" } + val response = DevToolsMessageSender.send( + "POST", + "/connection", + DevToolsRequest(targetUrl) + ) + return response.success + } + + fun startIntercept(): Boolean { + val headers = TestSessionHeadersProcessor.retrieveHeaders() + if (headers.isEmpty()) return false + logger.debug { "Start intercepting. Headers: $headers, sessionId: $sessionId" } + val response = DevToolsMessageSender.send( + "POST", + "/intercept", + DevToolInterceptRequest(targetUrl, params = mapOf("headers" to headers)) + ) + return response.success + } + + + fun stopIntercept(): Boolean { + logger.debug { "Stop intercepting: $targetUrl, sessionId $sessionId" } + val response = DevToolsMessageSender.send( + "DELETE", + "/intercept", + DevToolInterceptRequest(targetUrl) + ) + setHeaders(mapOf()) + return response.success + } + + private fun setHeaders( + params: Map + ): Boolean = executeCommand( + "Network.setExtraHTTPHeaders", + DevToolsHeaderRequest(targetUrl, sessionId.sessionId, mapOf("headers" to params)) + ).success + + @Deprecated(message = "Useless") + private fun autoAttach(): Boolean { + val params = mapOf("autoAttach" to true, "waitForDebuggerOnStart" to false).toOutput() + return executeCommand( + "Target.setAutoAttach", + DevToolsRequest(targetUrl, sessionId.sessionId, params = params) + ).success + } + + private fun attachToTarget(targetId: String): SessionId? { + val params = mapOf("targetId" to targetId, "flatten" to true).toOutput() + val response = executeCommand( + "Target.attachToTarget", + DevToolsRequest(target = targetUrl, params = params), + SessionId::class + ) + return response.takeIf(ResponseStatus::success) + ?.let(ResponseStatus::content) + } + + private fun retrieveTargetId(currentUrl: String): String? = targets() + .find { it.url == currentUrl } + ?.targetId + ?.uppercase(Locale.getDefault()) + ?.takeIf { it.isNotBlank() } + + private fun targets(): List = executeCommand( + "Target.getTargets", + DevToolsRequest(target = targetUrl), + TargetInfos::class + ).takeIf(ResponseStatus::success) + ?.let(ResponseStatus::content) + ?.let(TargetInfos::targetInfos) + ?: emptyList() + + private fun executeCommand( + commandName: String, + request: DevToolsMessage, + httpMethod: String = "POST" + ): ResponseStatus = DevToolsMessageSender.send(httpMethod, "/command/$commandName", request) + + private fun executeCommand( + commandName: String, + request: DevToolsMessage, + clazz: KClass, + httpMethod: String = "POST" + ): ResponseStatus = DevToolsMessageSender.send(httpMethod, "/command/$commandName", request, clazz) + + private fun Map.toOutput(): Map = mapValues { (_, value) -> + val serializer = value::class.serializer().cast() + json.encodeToJsonElement(serializer, value) + } + + @Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST") + private inline fun KSerializer.cast(): KSerializer = this as KSerializer + + private inline fun trackTime(tag: String = "", debug: Boolean = false, block: () -> T) = + measureTimedValue { block() }.apply { + val logger = KotlinLogging.logger {} + val message = "[$tag] took: $duration" + when { + duration.toDouble(DurationUnit.SECONDS) > 1 -> { + logger.warn { message } + } + + duration.toDouble(DurationUnit.SECONDS) > 30 -> { + logger.error { message } + } + + else -> if (debug) logger.debug { message } else logger.trace { message } + } + }.value + +} diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/selenium/DevToolStorage.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/selenium/DevToolStorage.kt new file mode 100644 index 00000000..4c5e7a54 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/selenium/DevToolStorage.kt @@ -0,0 +1,32 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.selenium + +import mu.KotlinLogging + +object DevToolStorage { + private val logger = KotlinLogging.logger {} + private val storage: InheritableThreadLocal = InheritableThreadLocal() + + fun set(devtool: ChromeDevTool) { + storage.set(devtool) + logger.debug { "DevTool inited for: Thread id=${Thread.currentThread().id}, DevToolWS address=$devtool" } + } + + fun get(): ChromeDevTool? = storage.get() + + fun clear() = storage.remove() +} diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/selenium/Model.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/selenium/Model.kt new file mode 100644 index 00000000..21560fbc --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/selenium/Model.kt @@ -0,0 +1,61 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.selenium + +import kotlinx.serialization.Serializable +import kotlinx.serialization.json.JsonElement +import com.epam.drill.agent.common.transport.AgentMessage + +@Serializable +data class TargetInfos(val targetInfos: List): AgentMessage() + +@Serializable +data class SessionId(val sessionId: String = ""): AgentMessage() + +@Serializable +data class Target( + val targetId: String, + val type: String, + val title: String, + val url: String, + val attached: Boolean, + val browserContextId: String, +) + +@Serializable +sealed class DevToolsMessage : AgentMessage() { + abstract val target: String +} + +@Serializable +data class DevToolsRequest( + override val target: String, + val sessionId: String = "", + val params: Map = emptyMap() +) : DevToolsMessage() + +@Serializable +data class DevToolInterceptRequest( + override val target: String, + val params: Map> = emptyMap() +) : DevToolsMessage() + +@Serializable +data class DevToolsHeaderRequest( + override val target: String, + val sessionId: String, + val params: Map> = emptyMap(), +) : DevToolsMessage() diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/selenium/SeleniumTransformer.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/selenium/SeleniumTransformer.kt new file mode 100644 index 00000000..56195b92 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/selenium/SeleniumTransformer.kt @@ -0,0 +1,278 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.selenium + +import com.epam.drill.agent.configuration.Configuration +import com.epam.drill.agent.configuration.ParameterDefinitions +import com.epam.drill.agent.test.* +import com.epam.drill.agent.instrument.InstrumentationParameterDefinitions.INSTRUMENTATION_SELENIUM_ENABLED +import com.epam.drill.agent.instrument.TransformerObject +import com.epam.drill.agent.test.instrument.AbstractTestTransformerObject +import javassist.* +import java.io.* +import mu.KotlinLogging +import java.security.ProtectionDomain + +@Suppress("PrivatePropertyName") +actual object SeleniumTransformer : TransformerObject, AbstractTestTransformerObject() { + + private const val Command = "org.openqa.selenium.remote.Command" + private const val ImmutableMap = "com.google.common.collect.ImmutableMap" + private const val ImmutableList = "com.google.common.collect.ImmutableList" + private const val Cookie = "org.openqa.selenium.Cookie" + private const val DesiredCapabilities = "org.openqa.selenium.remote.DesiredCapabilities" + private const val Proxy = "org.openqa.selenium.Proxy" + private const val initPages = "\"about:blank\", \"data:,\"" + private const val EXTENSION_NAME = "header-transmitter.xpi" + private val FirefoxDriver = "org.openqa.selenium.firefox.FirefoxDriver" + + private val extensionFile: String + + internal const val addDrillCookiesMethod = "addDrillCookies" + private const val isFirefoxBrowser = "isFirefoxBrowser" + + override val logger = KotlinLogging.logger {} + + init { + val extension = this::class.java.getResource("/$EXTENSION_NAME") + File(System.getProperty("java.io.tmpdir")).resolve(EXTENSION_NAME).apply { + extensionFile = absolutePath + writeBytes(extension.readBytes()) + } + } + + override fun enabled() = super.enabled() && agentConfiguration.parameters[INSTRUMENTATION_SELENIUM_ENABLED] + + override fun permit(className: String, superName: String?, interfaces: Array): Boolean { + return className == "org/openqa/selenium/remote/RemoteWebDriver" + } + + override fun instrument( + ctClass: CtClass, + pool: ClassPool, + classLoader: ClassLoader?, + protectionDomain: ProtectionDomain? + ) { + logger.debug { "starting instrument ${ctClass.name}..." } + + var remoteWebDriverConstructorInstrumented = false; + + fun tryCatch(fn: () -> Any) { + try { + fn(); + remoteWebDriverConstructorInstrumented = true; + } catch (e: Exception) { + logger.debug { "failed to instrument: $e" } + } + } + + ctClass.addField(CtField.make("java.lang.String drillRemoteAddress;", ctClass)) + + tryCatch { + ctClass + .getConstructor("(Lorg/openqa/selenium/remote/CommandExecutor;Lorg/openqa/selenium/Capabilities;)V") + .insertBefore( + """ + try { + if ($1 instanceof org.openqa.selenium.remote.HttpCommandExecutor) { + java.lang.System.out.println("Constructor called - (Lorg/openqa/selenium/remote/CommandExecutor;Lorg/openqa/selenium/Capabilities;)V"); + org.openqa.selenium.remote.HttpCommandExecutor drillHttpCommandExecutor = (org.openqa.selenium.remote.HttpCommandExecutor) $1; + drillRemoteAddress = drillHttpCommandExecutor.getAddressOfRemoteServer().getAuthority(); + } + } catch (Exception e) { + java.lang.System.out.println( + "Drill4J: failed to get remote address - Constructor: RemoteWebDriver(CommandExecutor executor, Capabilities desiredCapabilities) - Error: " + e.toString() + ); + } + """.trimIndent() + ) + } + + tryCatch { + ctClass + .getConstructor("(Ljava/net/URL;Lorg/openqa/selenium/Capabilities;)V") + .insertBefore( + """ + try { + java.lang.System.out.println("Constructor called - (Ljava/net/URL;Lorg/openqa/selenium/Capabilities;)V"); + + drillRemoteAddress = $1.getAuthority(); + } catch (Exception e) { + java.lang.System.out.println( + "Drill4J: failed to get remote address - Constructor: RemoteWebDriver(URL remoteAddress, Capabilities desiredCapabilities) - Error: " + e.toString() + ); + } + """.trimIndent() + ) + } + + tryCatch { + ctClass + .getConstructor("(Ljava/net/URL;Lorg/openqa/selenium/Capabilities;Lorg/openqa/selenium/Capabilities;)V") + .insertBefore( + """ + try { + java.lang.System.out.println("Constructor called - (Ljava/net/URL;Lorg/openqa/selenium/Capabilities;Lorg/openqa/selenium/Capabilities;)V"); + drillRemoteAddress = $1.getAuthority(); + } catch (Exception e) { + java.lang.System.out.println( + "Drill4J: failed to get remote address - Constructor: RemoteWebDriver(URL remoteAddress, Capabilities desiredCapabilities, Capabilities requiredCapabilities) - Error: " + e.toString() + ); + } + """.trimIndent() + ) + } + + tryCatch { + ctClass + .getConstructor("(Lorg/openqa/selenium/Capabilities;)V") + .insertBefore( + """ + java.lang.System.out.println("Constructor called - (Lorg/openqa/selenium/Capabilities;)V"); + """.trimIndent() + ) + } + + if (!remoteWebDriverConstructorInstrumented) { + logger.warn { "No RemoteWebDriver constructors were instrumented. Possibly unsupported Selenium version" } + } else { + logger.debug { "RemoteWebDriver constructors instrumented" } + } + + ctClass.addMethod( + CtMethod.make( + """ + public boolean $isFirefoxBrowser(org.openqa.selenium.Capabilities capabilities){ + return capabilities.getBrowserName().equalsIgnoreCase("firefox"); + } + """.trimIndent(), + ctClass + ) + ) + + val startSession = ctClass.getDeclaredMethod("startSession") + + /** + * Browser proxy is needed only for Firefox browser + */ + startSession.insertBefore( + """ + if (${this::class.java.name}.INSTANCE.${this::proxyUrl.name}() != null && $isFirefoxBrowser($1)) { + $DesiredCapabilities dCap = new $DesiredCapabilities(); + $Proxy dProxy = new $Proxy(); + dProxy.setHttpProxy(${this::class.java.name}.INSTANCE.${this::proxyUrl.name}()); + dProxy.setSslProxy(${this::class.java.name}.INSTANCE.${this::proxyUrl.name}()); + dCap.setCapability("proxy", dProxy); + $1 = $1.merge(dCap); + } + ${WebDriverThreadStorage::class.java.name}.INSTANCE.${WebDriverThreadStorage::set.name}(this); + """ + ) + startSession.insertAfter( + """ + if (${this::class.java.name}.INSTANCE.${this::devToolsProxyAddress.name}() != null){ + ${ChromeDevTool::class.java.name} drillDevTools = new ${ChromeDevTool::class.java.name}( + ((java.util.Map)this.capabilities.getCapability("goog:chromeOptions")), + drillRemoteAddress + ); + drillDevTools.${ChromeDevTool::connect.name}(sessionId.toString(), getCurrentUrl()); + } + try { + if (this instanceof $FirefoxDriver) { + java.util.HashMap hashMapq = new java.util.HashMap(); + hashMapq.put("path", "${extensionFile.replace("\\", "\\\\")}"); + hashMapq.put("temporary", Boolean.TRUE); + this.execute("installExtension", hashMapq).getValue(); + } + } catch (Exception e){} + """ + ) + ctClass.addMethod( + CtMethod.make( + """ + public void $addDrillCookiesMethod() { + if ($isFirefoxBrowser(getCapabilities()) && $ARE_DRILL_HEADERS_PRESENT) { + try { + executor.execute(new $Command(sessionId, "addCookie", $ImmutableMap.of("cookie", new $Cookie($SESSION_ID_CALC_LINE)))); + executor.execute(new $Command(sessionId, "addCookie", $ImmutableMap.of("cookie", new $Cookie($TEST_NAME_CALC_LINE)))); + } catch(Exception e) { e.printStackTrace();} + } + } + """.trimIndent(), + ctClass + ) + ) + ctClass.addMethod( + CtMethod.make( + """ + public void addDrillHeaders() { + if ($IS_DEV_TOOL_NOT_NULL && $ARE_DRILL_HEADERS_PRESENT && !$IS_HEADER_ADDED) { + try { + java.util.HashMap hashMap = new java.util.HashMap(); + hashMap.put($SESSION_ID_CALC_LINE); + hashMap.put($TEST_NAME_CALC_LINE); + ${getChromeDevTool()}.${ChromeDevTool::addHeaders.name}(hashMap); + } catch(Exception e) { e.printStackTrace();} + } + } + """.trimIndent(), + ctClass + ) + ) + ctClass.getDeclaredMethod("get").insertBefore( + """ + boolean isInitPage = $ImmutableList.of($initPages).contains(getCurrentUrl()); + if (isInitPage) { execute("get", $ImmutableMap.of("url", $1)); } + addDrillHeaders(); + $addDrillCookiesMethod(); + """.trimIndent() + ) + /** + * todo enable js instrumentation on tab open @Roman_Davliatshin + */ + ctClass.getMethod( + "execute", + "(Ljava/lang/String;Ljava/util/Map;)Lorg/openqa/selenium/remote/Response;" + ).insertAfter( + """ + if ($1.equals(org.openqa.selenium.remote.DriverCommand.SWITCH_TO_WINDOW)){ + java.lang.String currentUrl = getCurrentUrl(); + if ($IS_DEV_TOOL_NOT_NULL){ + ${getChromeDevTool()}.${ChromeDevTool::switchSession.name}(currentUrl); + } else { + execute("get", $ImmutableMap.of("url",currentUrl)); + $addDrillCookiesMethod(); + } + } + """.trimIndent() + ) + ctClass.getDeclaredMethod("quit").insertBefore( + """ + if ($IS_DEV_TOOL_NOT_NULL){ + ${getChromeDevTool()}.${ChromeDevTool::close.name}(); + ${DevToolStorage::class.java.name}.INSTANCE.${DevToolStorage::clear.name}(); + } + ${WebDriverThreadStorage::class.java.name}.INSTANCE.${WebDriverThreadStorage::clear.name}(); + """.trimIndent() + ) + } + + fun proxyUrl() = Configuration.parameters[ParameterDefinitions.PROXY_ADDRESS] + + fun devToolsProxyAddress() = Configuration.parameters[ParameterDefinitions.DEVTOOLS_PROXY_ADDRESS] + + private fun getChromeDevTool() = "${DevToolStorage::class.java.name}.INSTANCE.${DevToolStorage::get.name}()" +} diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/selenium/WebDriverThreadStorage.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/selenium/WebDriverThreadStorage.kt new file mode 100644 index 00000000..a2d5319b --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/selenium/WebDriverThreadStorage.kt @@ -0,0 +1,32 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.selenium + +object WebDriverThreadStorage { + private val webDriver = InheritableThreadLocal() + + fun set(obj: Any) = webDriver.set(obj) + + fun addCookies() { + runCatching { + webDriver.get()?.let { + it.javaClass.getMethod(SeleniumTransformer.addDrillCookiesMethod).invoke(it) + } + }.getOrNull() + } + + fun clear() = webDriver.remove() +} diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/testng/AbstractTestNG67Transformer.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/testng/AbstractTestNG67Transformer.kt new file mode 100644 index 00000000..b0eddae6 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/testng/AbstractTestNG67Transformer.kt @@ -0,0 +1,202 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.testng + +import com.epam.drill.agent.test.execution.TestController +import javassist.* +import java.lang.reflect.* +import java.security.* +import mu.KotlinLogging + +abstract class AbstractTestNG67Transformer() : AbstractTestNGTransformer() { + companion object { + const val engineSegment = "testng" + const val TestNGMethod = "org.testng.internal.TestNGMethod" + const val ITestResult = "org.testng.ITestResult" + + private const val DrillTestNGTestListner = "DrillTestNGTestListener" + private const val ITestContext = "org.testng.ITestContext" + } + + override val logger = KotlinLogging.logger {} + + abstract val versionRegex: Regex + + override fun permit(className: String, superName: String?, interfaces: Array): Boolean { + return className == "org/testng/TestRunner" + } + + override fun instrument( + ctClass: CtClass, + pool: ClassPool, + classLoader: ClassLoader?, + protectionDomain: ProtectionDomain?, + ) { + createTestListener(pool, classLoader, protectionDomain) + ctClass.constructors.forEach { it.insertAfter("addTestListener(new $DrillTestNGTestListner());") } + ctClass.supportIgnoredTestsTracking() + } + + private fun createTestListener( + pool: ClassPool, + classLoader: ClassLoader?, + protectionDomain: ProtectionDomain?, + ) { + val testListener = pool.makeClass(DrillTestNGTestListner) + testListener.interfaces = arrayOf(pool.get("org.testng.ITestListener")) + testListener.addMethod( + CtMethod.make(getParamsMethod(), testListener) + ) + testListener.addMethod( + CtMethod.make(getTestClassNameMethod(), testListener) + ) + testListener.addMethod( + CtMethod.make(getTestNameMethod(), testListener) + ) + testListener.addMethod( + CtMethod.make(getTestGroups(), testListener) + ) + testListener.addMethod( + CtMethod.make( + """ + public void onTestStart($ITestResult result) { + if (result.getThrowable() == null) { + ${TestController::class.java.name}.INSTANCE.${TestController::testStarted.name}("$engineSegment", getTestClassName(result), getTestName(result), getParamsString(result), getTestGroups(result)); + } else { + ${this::class.java.name}.INSTANCE.${this::debug.name}("The start of the test " + result.getName() + " is ignored by the drill"); + } + } + """.trimIndent(), + testListener + ) + ) + testListener.addMethod( + CtMethod.make( + """ + public void onTestSuccess($ITestResult result) { + ${TestController::class.java.name}.INSTANCE.${TestController::testFinished.name}("$engineSegment", getTestClassName(result), getTestName(result), "PASSED", getParamsString(result), getTestGroups(result)); + } + """.trimIndent(), + testListener + ) + ) + testListener.addMethod( + CtMethod.make( + """ + public void onTestFailure($ITestResult result) { + ${TestController::class.java.name}.INSTANCE.${TestController::testFinished.name}("$engineSegment", getTestClassName(result), getTestName(result), "FAILED", getParamsString(result), getTestGroups(result)); + } + """.trimIndent(), + testListener + ) + ) + testListener.addMethod( + CtMethod.make( + """ + public void onTestSkipped($ITestResult result) { + ${TestController::class.java.name}.INSTANCE.${TestController::testIgnored.name}("$engineSegment", getTestClassName(result), getTestName(result), getParamsString(result), getTestGroups(result)); + } + """.trimIndent(), + testListener + ) + ) + testListener.addMethod( + CtMethod.make( + """ + public void onTestFailedButWithinSuccessPercentage($ITestResult result) { + return; + } + """.trimIndent(), + testListener + ) + ) + testListener.addMethod( + CtMethod.make( + """ + public void onStart($ITestContext result) { + return; + } + """.trimIndent(), + testListener + ) + ) + testListener.addMethod( + CtMethod.make( + """ + public void onFinish($ITestContext result) { + return; + } + """.trimIndent(), + testListener + ) + ) + testListener.toClass(classLoader, protectionDomain) + } + + protected open fun getParamsMethod(): String = """ + private String getParamsString($ITestResult result) { + Object[] parameters = result.getParameters(); + String paramString = ${this::class.java.name}.INSTANCE.${this::paramTypes.name}(parameters); + return paramString; + } + """.trimIndent() + + protected open fun getTestClassNameMethod(): String = """ + private String getTestClassName($ITestResult result) { + return result.getInstanceName(); + } + """.trimIndent() + + protected open fun getTestNameMethod(): String = """ + private String getTestName($ITestResult result) { + return result.getName(); + } + """.trimIndent() + + protected open fun getTestGroups(): String = """ + private java.util.List getTestGroups($ITestResult result) { + String[] groups = result.getMethod().getGroups(); + return java.util.Arrays.asList(groups); + } + """.trimIndent() + + + private fun CtClass.supportIgnoredTestsTracking() = getDeclaredMethod("run").insertBefore( + """ + java.util.Iterator disabledTests = getExcludedMethods().iterator(); + while(disabledTests.hasNext()) { + java.lang.Object baseMethod = disabledTests.next(); + if (baseMethod instanceof $TestNGMethod) { + $TestNGMethod test = ($TestNGMethod) baseMethod; + ${TestController::class.java.name}.INSTANCE.${TestController::testIgnored.name}("$engineSegment", test.getTestClass().getName(), test.getMethodName()); + } + } + """.trimIndent() + ) + + fun paramTypes(objects: Array?): String = objects?.joinToString(",", "(", ")") { obj -> + when (obj) { + null -> obj.toString() + is Field -> obj.type.simpleName + else -> obj.javaClass.simpleName.substringBeforeLast("\$") + } + } ?: "" + + fun debug(message: String) { + logger.debug { message } + } + +} diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/testng/AbstractTestNGPrioritizingTransformer.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/testng/AbstractTestNGPrioritizingTransformer.kt new file mode 100644 index 00000000..85edecf0 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/testng/AbstractTestNGPrioritizingTransformer.kt @@ -0,0 +1,80 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.testng + +import com.epam.drill.agent.configuration.ParameterDefinitions +import com.epam.drill.agent.test.prioritization.RecommendedTests +import javassist.ClassPool +import javassist.CtClass +import java.security.ProtectionDomain + +abstract class AbstractTestNGPrioritizingTransformer() : AbstractTestNGTransformer() { + private val engineSegment = "testng" + abstract val versionRegex: Regex + abstract fun getMethodParametersExpression(): String + + override fun enabled(): Boolean = super.enabled() && agentConfiguration.parameters[ParameterDefinitions.RECOMMENDED_TESTS_ENABLED] + + override fun permit(className: String, superName: String?, interfaces: Array): Boolean { + return interfaces.any { it == "org/testng/IMethodSelector" } + } + + override fun instrument( + ctClass: CtClass, + pool: ClassPool, + classLoader: ClassLoader?, + protectionDomain: ProtectionDomain?, + ) { + if ("${ctClass.url}".contains(versionRegex)) { + instrumentIfSupport(ctClass, pool, classLoader, protectionDomain) + } + } + + private fun instrumentIfSupport( + ctClass: CtClass, + pool: ClassPool, + classLoader: ClassLoader?, + protectionDomain: ProtectionDomain?, + ): ByteArray? { + instrumentIncludeMethod(ctClass) + return ctClass.toBytecode() + } + + private fun instrumentIncludeMethod(ctClass: CtClass) { + ctClass.getMethod( + "includeMethod", + "(Lorg/testng/IMethodSelectorContext;Lorg/testng/ITestNGMethod;Z)Z" + ).insertAfter( + """ + if (${'$'}_ == true && $3 == true) { + java.lang.String className = $2.getTestClass().getName(); + java.lang.String methodName = $2.getMethodName(); + java.lang.String methodParameters = ${this::class.java.name}.INSTANCE.${this::paramTypes.name}($2.${getMethodParametersExpression()}); + boolean shouldSkip = ${RecommendedTests::class.java.name}.INSTANCE.${RecommendedTests::shouldSkip.name}("$engineSegment", className, methodName, methodParameters); + if (shouldSkip) { + return false; + } + } + """.trimIndent() + ) + } + + @Suppress("MemberVisibilityCanBePrivate") + fun paramTypes(objects: Array?>?): String = objects?.joinToString(",", "(", ")") { obj -> + obj?.name ?: "" + } ?: "" + +} \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/testng/AbstractTestNGTransformer.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/testng/AbstractTestNGTransformer.kt new file mode 100644 index 00000000..5e8ba129 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/testng/AbstractTestNGTransformer.kt @@ -0,0 +1,23 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.testng + +import com.epam.drill.agent.instrument.InstrumentationParameterDefinitions.INSTRUMENTATION_TESTNG_ENABLED +import com.epam.drill.agent.test.instrument.AbstractTestTransformerObject + +abstract class AbstractTestNGTransformer(): AbstractTestTransformerObject() { + override fun enabled() = super.enabled() && agentConfiguration.parameters[INSTRUMENTATION_TESTNG_ENABLED] +} \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG6PrioritizingTransformer.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG6PrioritizingTransformer.kt new file mode 100644 index 00000000..b028106e --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG6PrioritizingTransformer.kt @@ -0,0 +1,28 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.testng + +import com.epam.drill.agent.instrument.TransformerObject +import mu.KotlinLogging + +actual object TestNG6PrioritizingTransformer: TransformerObject, AbstractTestNGPrioritizingTransformer() { + override val logger = KotlinLogging.logger {} + override val versionRegex: Regex = "testng-6\\.[0-9]+(\\.[0-9]+)*".toRegex() + + override fun getMethodParametersExpression(): String { + return "getConstructorOrMethod().getParameterTypes()" + } +} \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG6Transformer.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG6Transformer.kt new file mode 100644 index 00000000..b96b433f --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG6Transformer.kt @@ -0,0 +1,42 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.testng + +import com.epam.drill.agent.instrument.TransformerObject +import javassist.* +import java.security.* + +actual object TestNG6Transformer : TransformerObject, AbstractTestNG67Transformer() { + override val versionRegex: Regex = "testng-6\\.[0-9]+(\\.[0-9]+)*".toRegex() + + override fun instrument( + ctClass: CtClass, + pool: ClassPool, + classLoader: ClassLoader?, + protectionDomain: ProtectionDomain?, + ) { + if ("${ctClass.url}".contains(versionRegex)) { + super.instrument(ctClass, pool, classLoader, protectionDomain) + } + } + + + override fun getTestClassNameMethod(): String = """ + private String getTestClassName($ITestResult result) { + return result.getTestClass().getName(); + } + """.trimIndent() +} diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG7PrioritizingTransformer.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG7PrioritizingTransformer.kt new file mode 100644 index 00000000..c0c9221b --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG7PrioritizingTransformer.kt @@ -0,0 +1,28 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.testng + +import com.epam.drill.agent.instrument.TransformerObject +import mu.KotlinLogging + +actual object TestNG7PrioritizingTransformer: TransformerObject, AbstractTestNGPrioritizingTransformer() { + override val logger = KotlinLogging.logger {} + override val versionRegex: Regex = "testng-7\\.[0-9]+(\\.[0-9]+)*".toRegex() + + override fun getMethodParametersExpression(): String { + return "getParameterTypes()" + } +} \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG7Transformer.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG7Transformer.kt new file mode 100644 index 00000000..2018db17 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG7Transformer.kt @@ -0,0 +1,36 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.testng + +import com.epam.drill.agent.instrument.TransformerObject +import javassist.* +import java.security.* + +actual object TestNG7Transformer : TransformerObject, AbstractTestNG67Transformer() { + + override val versionRegex: Regex = "testng-7\\.[0-9]+(\\.[0-9]+)*".toRegex() + + override fun instrument( + ctClass: CtClass, + pool: ClassPool, + classLoader: ClassLoader?, + protectionDomain: ProtectionDomain?, + ) { + if ("${ctClass.url}".contains(versionRegex)) { + super.instrument(ctClass, pool, classLoader, protectionDomain) + } + } +} diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/prioritization/RecommendedTests.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/prioritization/RecommendedTests.kt new file mode 100644 index 00000000..b2b75bcb --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/prioritization/RecommendedTests.kt @@ -0,0 +1,59 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.prioritization + +import com.epam.drill.agent.test.execution.TestMethodInfo +import mu.KotlinLogging + +object RecommendedTests { + private val logger = KotlinLogging.logger {} + private val recommendedTestsReceiver: RecommendedTestsReceiver = RecommendedTestsReceiverImpl() + private val testsToSkip: Set by lazy { initTestsToSkip() } + + private fun initTestsToSkip() = recommendedTestsReceiver.getTestsToSkip() + .toSet() + .also { + logger.info { "${it.size} tests will be skipped by Drill4J" } + } + + + fun shouldSkip( + engine: String, + testClass: String, + testMethod: String, + methodParameters: String = "()" + ): Boolean { + val test = TestMethodInfo( + engine = engine, + className = testClass, + method = testMethod, + methodParams = methodParameters, + ) + return shouldSkipByTestMethod(test) + } + + fun shouldSkipByTestMethod(test: TestMethodInfo): Boolean { + return testsToSkip.contains(test).also { + if (it) { + logger.debug { "Test `${test.method}` will be skipped by Drill4J" } + recommendedTestsReceiver.sendSkippedTest(test) + } else { + logger.debug { "Test `${test.method}` will not be skipped by Drill4J" } + } + } + } + +} \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/prioritization/RecommendedTestsReceiver.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/prioritization/RecommendedTestsReceiver.kt new file mode 100644 index 00000000..45de6b1a --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/prioritization/RecommendedTestsReceiver.kt @@ -0,0 +1,125 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.prioritization + +import com.epam.drill.agent.common.transport.AgentMessageDestination +import com.epam.drill.agent.common.transport.AgentMessageReceiver +import com.epam.drill.agent.configuration.Configuration +import com.epam.drill.agent.configuration.DefaultParameterDefinitions +import com.epam.drill.agent.configuration.ParameterDefinitions +import com.epam.drill.agent.test.execution.TestController +import com.epam.drill.agent.test.execution.TestExecutionRecorder +import com.epam.drill.agent.test.execution.TestMethodInfo +import com.epam.drill.agent.test.sending.* +import com.epam.drill.agent.test.transport.TestAgentMessageReceiver +import kotlinx.serialization.Serializable +import mu.KotlinLogging + +interface RecommendedTestsReceiver { + fun getTestsToSkip(): List + fun sendSkippedTest(test: TestMethodInfo) +} + +class RecommendedTestsReceiverImpl( + private val agentMessageReceiver: AgentMessageReceiver = TestAgentMessageReceiver, + private val testExecutionRecorder: TestExecutionRecorder = TestController +) : RecommendedTestsReceiver { + private val logger = KotlinLogging.logger {} + + override fun getTestsToSkip(): List { + if (!Configuration.parameters[ParameterDefinitions.RECOMMENDED_TESTS_ENABLED]) + return emptyList() + val groupId = Configuration.parameters[DefaultParameterDefinitions.GROUP_ID] + val testTaskId = Configuration.parameters[ParameterDefinitions.TEST_TASK_ID] + val targetAppId = Configuration.parameters[ParameterDefinitions.RECOMMENDED_TESTS_TARGET_APP_ID] + val targetBuildVersion = Configuration.parameters[ParameterDefinitions.RECOMMENDED_TESTS_TARGET_BUILD_VERSION] + .takeIf { it.isNotEmpty() } + val targetCommitSha = Configuration.parameters[ParameterDefinitions.RECOMMENDED_TESTS_TARGET_COMMIT_SHA] + .takeIf { it.isNotEmpty() } + val baselineCommitSha = Configuration.parameters[ParameterDefinitions.RECOMMENDED_TESTS_BASELINE_COMMIT_SHA] + .takeIf { it.isNotEmpty() } + val baselineBuildVersion = + Configuration.parameters[ParameterDefinitions.RECOMMENDED_TESTS_BASELINE_BUILD_VERSION] + .takeIf { it.isNotEmpty() } + val coveragePeriodDays = + Configuration.parameters[ParameterDefinitions.RECOMMENDED_TESTS_COVERAGE_PERIOD_DAYS].toInt() + .takeIf { it > 0 } + val useMaterializedViews = + Configuration.parameters[ParameterDefinitions.RECOMMENDED_TESTS_USE_MATERIALIZED_VIEWS] + .takeIf { it.isNotEmpty() } + ?.lowercase() + ?.toBooleanStrict() + + val parameters: String = buildString { + append("?groupId=$groupId") + append("&appId=$targetAppId") + append("&testTaskId=$testTaskId") + append("&testsToSkip=true") + targetBuildVersion?.let { append("&targetBuildVersion=$it") } + targetCommitSha?.let { append("&targetCommitSha=$it") } + baselineCommitSha?.let { append("&baselineCommitSha=$it") } + baselineBuildVersion?.let { append("&baselineBuildVersion=$it") } + coveragePeriodDays?.let { append("&coveragePeriodDays=$it") } + useMaterializedViews?.let { append("&useMaterializedViews=$it") } + } + logger.debug { "Retrieving information about recommended tests, testTaskId: $testTaskId" } + return runCatching { + agentMessageReceiver.receive( + AgentMessageDestination( + "GET", + "/recommended-tests$parameters", + ), + RecommendedTestsApiResponse::class + ).data.recommendedTests.map { it.toTestMethodInfo() } + }.onFailure { + logger.warn { "Unable to retrieve information about recommended tests. Error message: $it" } + }.getOrElse { + emptyList() + } + } + + override fun sendSkippedTest(test: TestMethodInfo) { + testExecutionRecorder.recordTestIgnoring(test, isSmartSkip = true) + } +} + +@Serializable +class RecommendedTestsApiResponse( + val data: RecommendedTestsResponse +) + +@Serializable +class RecommendedTestsResponse( + val recommendedTests: List +) + +@Serializable +class TestDefinitionResponse( + val testDefinitionId: String, + val testRunner: String, + val testPath: String, + val testName: String, + val tags: List, + val metadata: Map, +) + +private fun TestDefinitionResponse.toTestMethodInfo() = TestMethodInfo( + engine = testRunner, + className = testPath, + method = testName, + metadata = metadata, + tags = tags, +) \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/sending/AddTestsPayload.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/sending/AddTestsPayload.kt new file mode 100644 index 00000000..a67a6a89 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/sending/AddTestsPayload.kt @@ -0,0 +1,26 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.sending + +import com.epam.drill.agent.common.transport.AgentMessage +import kotlinx.serialization.Serializable + +@Serializable +data class AddTestsPayload( + val groupId: String, + val sessionId: String, + val tests: List = emptyList(), +): AgentMessage() \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/sending/TestDefinitionPayload.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/sending/TestDefinitionPayload.kt new file mode 100644 index 00000000..00e68fa4 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/sending/TestDefinitionPayload.kt @@ -0,0 +1,28 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.sending + +import kotlinx.serialization.Serializable + +@Serializable +class TestDefinitionPayload( + val runner: String = "", + val path: String = "", + val testName: String = "", + val testParams: List = emptyList(), + val metadata: Map = emptyMap(), + val tags: List = emptyList(), +) \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/sending/TestInfoSender.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/sending/TestInfoSender.kt new file mode 100644 index 00000000..b3db15bc --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/sending/TestInfoSender.kt @@ -0,0 +1,75 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.sending + +import com.epam.drill.agent.common.transport.AgentMessageDestination +import com.epam.drill.agent.common.transport.AgentMessageSender +import com.epam.drill.agent.configuration.Configuration +import com.epam.drill.agent.configuration.DefaultParameterDefinitions +import com.epam.drill.agent.configuration.ParameterDefinitions +import com.epam.drill.agent.test.session.SessionController +import mu.KotlinLogging +import java.util.concurrent.Executors +import java.util.concurrent.TimeUnit + +interface TestInfoSender { + fun startSendingTests() + fun stopSendingTests() +} + +class IntervalTestInfoSender( + private val messageSender: AgentMessageSender, + private val intervalMs: Long = 1000, + private val collectTests: () -> List = { emptyList() } +) : TestInfoSender { + private val logger = KotlinLogging.logger {} + private val scheduledThreadPool = Executors.newSingleThreadScheduledExecutor() + + override fun startSendingTests() { + scheduledThreadPool.scheduleAtFixedRate( + { sendTests(collectTests()) }, + 0, + intervalMs, + TimeUnit.MILLISECONDS + ) + logger.debug { "Test sending job is started." } + } + + override fun stopSendingTests() { + scheduledThreadPool.shutdown() + if (!scheduledThreadPool.awaitTermination(1, TimeUnit.SECONDS)) { + logger.error("Failed to send some tests prior to shutdown") + scheduledThreadPool.shutdownNow(); + } + sendTests(collectTests()) + messageSender.shutdown() + logger.info { "Test sending job is stopped." } + } + + private fun sendTests(tests: List) { + if (tests.isEmpty()) return + logger.debug { "Sending ${tests.size} tests..." } + messageSender.send( + destination = AgentMessageDestination("POST", "tests-metadata"), + message = AddTestsPayload( + groupId = Configuration.parameters[DefaultParameterDefinitions.GROUP_ID], + sessionId = SessionController.getSessionId(), + tests = tests + ), + serializer = AddTestsPayload.serializer() + ) + } +} \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/sending/TestLaunchPayload.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/sending/TestLaunchPayload.kt new file mode 100644 index 00000000..ee2196ee --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/sending/TestLaunchPayload.kt @@ -0,0 +1,28 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.sending + +import com.epam.drill.agent.test.execution.TestResult +import kotlinx.serialization.Serializable + +@Serializable +data class TestLaunchPayload( + val testLaunchId: String, + val testDefinitionId: String, + val result: TestResult, + val duration: Int?, + val details: TestDefinitionPayload, +) \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/serialization/Json.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/serialization/Json.kt new file mode 100644 index 00000000..bfdc44f8 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/serialization/Json.kt @@ -0,0 +1,23 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.serialization + +import kotlinx.serialization.json.* + +val json = Json { + encodeDefaults = true + ignoreUnknownKeys = true +} diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/serialization/PropertyDecoder.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/serialization/PropertyDecoder.kt new file mode 100644 index 00000000..2005423d --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/serialization/PropertyDecoder.kt @@ -0,0 +1,60 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.serialization + +import kotlinx.serialization.* +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* +import kotlinx.serialization.internal.* + +class PropertyDecoder(val map: Map) : NamedValueDecoder() { + private var currentIndex = 0 + + override fun decodeCollectionSize(descriptor: SerialDescriptor): Int { + return decodeTaggedInt(nested("size")) + } + + override fun decodeTaggedValue(tag: String): Any { + return map.getValue(tag) + } + + override fun decodeTaggedBoolean(tag: String): Boolean { + return map.getValue(tag) as Boolean + } + + override fun decodeTaggedLong(tag: String): Long { + return map.getValue(tag) as Long + } + + override fun decodeTaggedEnum(tag: String, enumDescriptor: SerialDescriptor): Int { + return (map.getValue(tag) as Enum<*>).ordinal + } + + @Suppress("UNCHECKED_CAST") + override fun decodeSerializableValue(deserializer: DeserializationStrategy, previousValue: T?): T { + return map[this.currentTagOrNull] as? T ?: super.decodeSerializableValue(deserializer, previousValue) + } + + override fun decodeElementIndex(descriptor: SerialDescriptor): Int { + val tag = nested("size") + val size = if (map.containsKey(tag)) decodeTaggedInt(tag) else descriptor.elementsCount + while (currentIndex < size) { + val name = descriptor.getTag(currentIndex++) + if (map.keys.any { it.startsWith(name) }) return currentIndex - 1 + } + return CompositeDecoder.DECODE_DONE + } +} diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/session/SessionController.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/session/SessionController.kt new file mode 100644 index 00000000..0fc214c7 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/session/SessionController.kt @@ -0,0 +1,118 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.session + +import com.benasher44.uuid.* +import com.epam.drill.agent.common.request.DrillInitialContext +import com.epam.drill.agent.common.request.DrillRequest +import com.epam.drill.agent.request.DrillRequestHolder +import com.epam.drill.agent.test.SESSION_ID_HEADER +import com.epam.drill.agent.configuration.Configuration +import com.epam.drill.agent.configuration.DefaultParameterDefinitions +import com.epam.drill.agent.configuration.ParameterDefinitions +import com.epam.drill.agent.test.sending.IntervalTestInfoSender +import com.epam.drill.agent.test.sending.TestInfoSender +import com.epam.drill.agent.test.execution.TestController +import com.epam.drill.agent.test.execution.TestExecutionInfo +import com.epam.drill.agent.test.sending.TestDefinitionPayload +import com.epam.drill.agent.test.sending.TestLaunchPayload +import com.epam.drill.agent.test.transport.TestAgentMessageSender +import mu.KotlinLogging +import java.time.Instant +import java.time.ZoneId +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter +import java.util.zip.CRC32 + +actual object SessionController { + private val logger = KotlinLogging.logger {} + private val sessionSender: SessionSender = SessionSenderImpl( + messageSender = TestAgentMessageSender + ) + private val testInfoSender: TestInfoSender = IntervalTestInfoSender( + messageSender = TestAgentMessageSender, + collectTests = { TestController.getFinishedTests().toTestLaunchPayloads() } + ) + private lateinit var sessionId: String + + init { + if (isTestLaunchMetadataSendingEnabled()) + Runtime.getRuntime().addShutdownHook(Thread { testInfoSender.stopSendingTests() }) + } + + actual fun startSession() { + if (!isTestAgentEnabled()) { + logger.info { "Test agent is disabled. Test session will not be started." } + return + } + val customSessionId = Configuration.parameters[ParameterDefinitions.SESSION_ID] + sessionId = customSessionId ?: uuid4().toString() + DrillInitialContext.add(SESSION_ID_HEADER, sessionId) + DrillRequestHolder.store(DrillRequest(sessionId)) + logger.info { "Test session started: $sessionId" } + val builds = + takeIf { Configuration.parameters[ParameterDefinitions.RECOMMENDED_TESTS_TARGET_APP_ID].isNotEmpty() }?.let { + SingleSessionBuildPayload( + appId = Configuration.parameters[ParameterDefinitions.RECOMMENDED_TESTS_TARGET_APP_ID], + buildVersion = Configuration.parameters[ParameterDefinitions.RECOMMENDED_TESTS_TARGET_BUILD_VERSION], + commitSha = Configuration.parameters[ParameterDefinitions.RECOMMENDED_TESTS_TARGET_COMMIT_SHA] + ) + }?.let { listOf(it) } ?: emptyList() + sessionSender.sendSession( + SessionPayload( + id = sessionId, + groupId = Configuration.parameters[DefaultParameterDefinitions.GROUP_ID], + testTaskId = Configuration.parameters[ParameterDefinitions.TEST_TASK_ID], + startedAt = System.currentTimeMillis().toIsoTimeFormat(), + builds = builds + ) + ) + if (isTestLaunchMetadataSendingEnabled()) + testInfoSender.startSendingTests() + } + + fun getSessionId(): String = sessionId + + private fun isTestAgentEnabled(): Boolean = Configuration.parameters[ParameterDefinitions.TEST_AGENT_ENABLED] + private fun isTestLaunchMetadataSendingEnabled(): Boolean = isTestAgentEnabled() && Configuration.parameters[ParameterDefinitions.TEST_LAUNCH_METADATA_SENDING_ENABLED] +} + +private fun List.toTestLaunchPayloads(): List = map { info -> + val testDefinitionPayload = TestDefinitionPayload( + runner = info.testMethod.engine, + path = info.testMethod.className, + testName = info.testMethod.method, + testParams = info.testMethod.methodParams.removeSurrounding("(", ")").split(",").filter { it.isNotEmpty() }, + metadata = info.testMethod.metadata, + tags = info.testMethod.tags + ) + TestLaunchPayload( + testLaunchId = info.testLaunchId, + testDefinitionId = hash(info.testMethod.signature), + result = info.result, + duration = info.finishedAt?.minus(info.startedAt ?: 0)?.toInt(), + details = testDefinitionPayload + ) +} + +private fun hash(signature: String): String = CRC32().let { + it.update(signature.toByteArray()) + java.lang.Long.toHexString(it.value) +} + +private fun Long.toIsoTimeFormat(): String = Instant.ofEpochMilli(this) + .let { ZonedDateTime.ofInstant(it, ZoneId.systemDefault()) } + .format(DateTimeFormatter.ISO_OFFSET_DATE_TIME) diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/session/SessionPayload.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/session/SessionPayload.kt new file mode 100644 index 00000000..8f142f84 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/session/SessionPayload.kt @@ -0,0 +1,36 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.session + +import com.epam.drill.agent.common.transport.AgentMessage +import kotlinx.serialization.Serializable + +@Serializable +class SingleSessionBuildPayload( + val appId: String, + val instanceId: String? = null, + val buildVersion: String? = null, + val commitSha: String? = null +) + +@Serializable +class SessionPayload( + val id: String, + val groupId: String, + val testTaskId: String, + val startedAt: String, + val builds: List = emptyList() +): AgentMessage() \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/session/SessionSender.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/session/SessionSender.kt new file mode 100644 index 00000000..f9948371 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/session/SessionSender.kt @@ -0,0 +1,37 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.session + +import com.epam.drill.agent.common.transport.AgentMessage +import com.epam.drill.agent.common.transport.AgentMessageDestination +import com.epam.drill.agent.common.transport.AgentMessageSender + +interface SessionSender { + fun sendSession(payload: SessionPayload) +} + +class SessionSenderImpl( + private val messageSender: AgentMessageSender +) : SessionSender { + + override fun sendSession(payload: SessionPayload) { + messageSender.send( + AgentMessageDestination("PUT", "sessions"), + payload, + SessionPayload.serializer() + ) + } +} \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/transport/TestAgentMessageReceiver.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/transport/TestAgentMessageReceiver.kt new file mode 100644 index 00000000..388d23d0 --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/transport/TestAgentMessageReceiver.kt @@ -0,0 +1,32 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.transport + +import com.epam.drill.agent.common.transport.AgentMessageReceiver +import com.epam.drill.agent.transport.JsonAgentMessageDeserializer +import com.epam.drill.agent.transport.SimpleAgentMessageReceiver +import com.epam.drill.agent.transport.HttpAgentMessageDestinationMapper + +object TestAgentMessageReceiver : AgentMessageReceiver by messageReceiver() + +fun messageReceiver(): AgentMessageReceiver { + return SimpleAgentMessageReceiver( + agentTransport(), + JsonAgentMessageDeserializer(), + HttpAgentMessageDestinationMapper("metrics") + ) +} + diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/transport/TestAgentMessageSender.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/transport/TestAgentMessageSender.kt new file mode 100644 index 00000000..67bfb79d --- /dev/null +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/test/transport/TestAgentMessageSender.kt @@ -0,0 +1,69 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.transport + +import java.io.File +import io.aesy.datasize.ByteUnit +import io.aesy.datasize.DataSize +import mu.KotlinLogging +import com.epam.drill.agent.configuration.DefaultParameterDefinitions +import com.epam.drill.agent.transport.* +import com.epam.drill.agent.transport.http.HttpAgentMessageTransport +import com.epam.drill.agent.common.transport.AgentMessageSender +import com.epam.drill.agent.configuration.Configuration +import com.epam.drill.agent.configuration.ParameterDefinitions + +private val logger = KotlinLogging.logger {} +private const val QUEUE_DEFAULT_SIZE: Long = 512L * 1024 * 1024 + +object TestAgentMessageSender : AgentMessageSender by messageSender() + +fun agentTransport(): AgentMessageTransport = HttpAgentMessageTransport( + Configuration.parameters[ParameterDefinitions.API_URL], + Configuration.parameters[ParameterDefinitions.API_KEY] ?: "", + Configuration.parameters[ParameterDefinitions.SSL_TRUSTSTORE] + ?.let { resolvePath(it) } ?: "", + Configuration.parameters[ParameterDefinitions.SSL_TRUSTSTORE_PASSWORD] ?: "", + gzipCompression = false +) + +fun messageSender(): AgentMessageSender { + val transport = agentTransport() + val serializer = JsonAgentMessageSerializer() + val mapper = HttpAgentMessageDestinationMapper("data-ingest") + val queue = InMemoryAgentMessageQueue( + capacity = Configuration.parameters[ParameterDefinitions.MESSAGE_QUEUE_LIMIT].let(::parseBytes) + ) + return QueuedAgentMessageSender(transport, serializer, mapper, queue, + maxRetries = Configuration.parameters[ParameterDefinitions.MESSAGE_MAX_RETRIES] + ) +} + +private fun resolvePath(path: String) = File(path).run { + val installationDir = File(Configuration.parameters[DefaultParameterDefinitions.INSTALLATION_DIR] ?: "") + val resolved = this.takeIf(File::exists) + ?: this.takeUnless(File::isAbsolute)?.let(installationDir::resolve) + logger.trace { "resolvePath: Resolved $path to ${resolved?.absolutePath}" } + resolved?.absolutePath ?: path +} + +private fun parseBytes(value: String): Long = value.run { + val logError: (Throwable) -> Unit = { logger.warn(it) { "parseBytes: Exception while parsing value: $this" } } + this.runCatching(DataSize::parse) + .onFailure(logError) + .getOrDefault(DataSize.of(QUEUE_DEFAULT_SIZE, ByteUnit.BYTE)) + .toUnit(ByteUnit.BYTE).value.toLong() +} \ No newline at end of file diff --git a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/transport/HttpAgentMessageDestinationMapper.kt b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/transport/HttpAgentMessageDestinationMapper.kt index d1b06e30..f8e7522c 100644 --- a/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/transport/HttpAgentMessageDestinationMapper.kt +++ b/java-agent/src/jvmMain/kotlin/com/epam/drill/agent/transport/HttpAgentMessageDestinationMapper.kt @@ -17,12 +17,10 @@ package com.epam.drill.agent.transport import com.epam.drill.agent.common.transport.AgentMessageDestination -class HttpAgentMessageDestinationMapper : AgentMessageDestinationMapper { - - private val dataIngestionPath = "data-ingest" +class HttpAgentMessageDestinationMapper(val path: String = "data-ingest") : AgentMessageDestinationMapper { override fun map(destination: AgentMessageDestination) = destination.copy( - target = if (destination.target.isEmpty()) dataIngestionPath else "${dataIngestionPath}/${destination.target}" + target = if (destination.target.isEmpty()) path else "${path}/${destination.target}" ) } diff --git a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/Agent.kt b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/Agent.kt index 02b37c5e..6b028fa7 100644 --- a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/Agent.kt +++ b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/Agent.kt @@ -64,6 +64,20 @@ import com.epam.drill.agent.instrument.undertow.UndertowHttpServerTransformer import com.epam.drill.agent.instrument.undertow.UndertowWsClientTransformer import com.epam.drill.agent.instrument.undertow.UndertowWsMessagesTransformer import com.epam.drill.agent.instrument.undertow.UndertowWsServerTransformer +import com.epam.drill.agent.test.instrument.cucumber.Cucumber4Transformer +import com.epam.drill.agent.test.instrument.cucumber.Cucumber5Transformer +import com.epam.drill.agent.test.instrument.cucumber.Cucumber6Transformer +import com.epam.drill.agent.test.instrument.jmeter.JMeterTransformer +import com.epam.drill.agent.test.instrument.junit.JUnit4PrioritizingTransformer +import com.epam.drill.agent.test.instrument.junit.JUnit4Transformer +import com.epam.drill.agent.test.instrument.junit.JUnit5Transformer +import com.epam.drill.agent.test.instrument.junit.JUnitPlatformPrioritizingTransformer +import com.epam.drill.agent.test.instrument.selenium.SeleniumTransformer +import com.epam.drill.agent.test.instrument.testng.TestNG6PrioritizingTransformer +import com.epam.drill.agent.test.instrument.testng.TestNG6Transformer +import com.epam.drill.agent.test.instrument.testng.TestNG7PrioritizingTransformer +import com.epam.drill.agent.test.instrument.testng.TestNG7Transformer +import com.epam.drill.agent.test.session.SessionController object Agent { @@ -109,6 +123,19 @@ object Agent { UndertowWsServerTransformer, UndertowWsMessagesTransformer, CompatibilityTestsTransformer, + JUnit4Transformer, + JUnit5Transformer, + TestNG6Transformer, + TestNG7Transformer, + Cucumber4Transformer, + Cucumber5Transformer, + Cucumber6Transformer, + SeleniumTransformer, + JMeterTransformer, + JUnit4PrioritizingTransformer, + JUnitPlatformPrioritizingTransformer, + TestNG6PrioritizingTransformer, + TestNG7PrioritizingTransformer, ) @OptIn(ExperimentalNativeApi::class, ExperimentalForeignApi::class) @@ -142,6 +169,8 @@ object Agent { Configuration.initializeJvm() loadJvmModule("com.epam.drill.agent.test2code.Test2Code") JvmModuleMessageSender.sendAgentMetadata() + + SessionController.startSession() } fun agentOnVmDeath() { diff --git a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/AbstractTestTransformerObject.kt b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/AbstractTestTransformerObject.kt new file mode 100644 index 00000000..6461ead5 --- /dev/null +++ b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/AbstractTestTransformerObject.kt @@ -0,0 +1,24 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument + +import com.epam.drill.agent.configuration.Configuration +import com.epam.drill.agent.configuration.ParameterDefinitions.TEST_AGENT_ENABLED +import com.epam.drill.agent.instrument.AbstractTransformerObject + +abstract class AbstractTestTransformerObject: AbstractTransformerObject() { + override fun enabled() = super.enabled() && Configuration.parameters[TEST_AGENT_ENABLED] +} \ No newline at end of file diff --git a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/AbstractCucumberTransformer.kt b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/AbstractCucumberTransformer.kt new file mode 100644 index 00000000..2bf426c8 --- /dev/null +++ b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/AbstractCucumberTransformer.kt @@ -0,0 +1,24 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.cucumber + +import com.epam.drill.agent.configuration.Configuration +import com.epam.drill.agent.instrument.InstrumentationParameterDefinitions.INSTRUMENTATION_CUCUMBER_ENABLED +import com.epam.drill.agent.test.instrument.AbstractTestTransformerObject + +abstract class AbstractCucumberTransformer(): AbstractTestTransformerObject() { + override fun enabled() = super.enabled() && Configuration.parameters[INSTRUMENTATION_CUCUMBER_ENABLED] +} \ No newline at end of file diff --git a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber4Transformer.kt b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber4Transformer.kt new file mode 100644 index 00000000..7cbbebaa --- /dev/null +++ b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber4Transformer.kt @@ -0,0 +1,24 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.cucumber + +import com.epam.drill.agent.instrument.TransformerObject + +actual object Cucumber4Transformer: TransformerObject, AbstractCucumberTransformer() { + override fun permit(className: String, superName: String?, interfaces: Array): Boolean { + return className == /*4.x.x*/"cucumber/runner/TestStep" + } +} \ No newline at end of file diff --git a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber5Transformer.kt b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber5Transformer.kt new file mode 100644 index 00000000..d1015838 --- /dev/null +++ b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber5Transformer.kt @@ -0,0 +1,24 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.cucumber + +import com.epam.drill.agent.instrument.TransformerObject + +actual object Cucumber5Transformer: TransformerObject, AbstractCucumberTransformer() { + override fun permit(className: String, superName: String?, interfaces: Array): Boolean { + return className == "io/cucumber/core/runner/TestStep" + } +} \ No newline at end of file diff --git a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber6Transformer.kt b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber6Transformer.kt new file mode 100644 index 00000000..cb1cfa6d --- /dev/null +++ b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/cucumber/Cucumber6Transformer.kt @@ -0,0 +1,24 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.cucumber + +import com.epam.drill.agent.instrument.TransformerObject + +actual object Cucumber6Transformer: TransformerObject, AbstractCucumberTransformer() { + override fun permit(className: String, superName: String?, interfaces: Array): Boolean { + return className == "io/cucumber/core/runner/TestStep" + } +} \ No newline at end of file diff --git a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/jmeter/JMeterTransformer.kt b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/jmeter/JMeterTransformer.kt new file mode 100644 index 00000000..c5e05183 --- /dev/null +++ b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/jmeter/JMeterTransformer.kt @@ -0,0 +1,29 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.jmeter + +import com.epam.drill.agent.configuration.Configuration +import com.epam.drill.agent.instrument.InstrumentationParameterDefinitions.INSTRUMENTATION_JMETER_ENABLED +import com.epam.drill.agent.instrument.TransformerObject +import com.epam.drill.agent.test.instrument.AbstractTestTransformerObject + +actual object JMeterTransformer: TransformerObject, AbstractTestTransformerObject() { + override fun enabled() = super.enabled() && Configuration.parameters[INSTRUMENTATION_JMETER_ENABLED] + + override fun permit(className: String, superName: String?, interfaces: Array): Boolean { + return className == "org/apache/jmeter/protocol/http/sampler/HTTPHC4Impl" + } +} \ No newline at end of file diff --git a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/junit/AbstractJUnitTransformer.kt b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/junit/AbstractJUnitTransformer.kt new file mode 100644 index 00000000..29cf2035 --- /dev/null +++ b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/junit/AbstractJUnitTransformer.kt @@ -0,0 +1,24 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.junit + +import com.epam.drill.agent.configuration.Configuration +import com.epam.drill.agent.instrument.InstrumentationParameterDefinitions.INSTRUMENTATION_JUNIT_ENABLED +import com.epam.drill.agent.test.instrument.AbstractTestTransformerObject + +abstract class AbstractJUnitTransformer(): AbstractTestTransformerObject() { + override fun enabled() = super.enabled() && Configuration.parameters[INSTRUMENTATION_JUNIT_ENABLED] +} \ No newline at end of file diff --git a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit4PrioritizingTransformer.kt b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit4PrioritizingTransformer.kt new file mode 100644 index 00000000..6d7dc12a --- /dev/null +++ b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit4PrioritizingTransformer.kt @@ -0,0 +1,28 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.junit + +import com.epam.drill.agent.configuration.Configuration +import com.epam.drill.agent.configuration.ParameterDefinitions +import com.epam.drill.agent.instrument.TransformerObject + +actual object JUnit4PrioritizingTransformer: TransformerObject, AbstractJUnitTransformer() { + override fun enabled(): Boolean = super.enabled() && Configuration.parameters[ParameterDefinitions.RECOMMENDED_TESTS_ENABLED] + + override fun permit(className: String, superName: String?, interfaces: Array): Boolean { + return className == "org/junit/runners/JUnit4" + } +} \ No newline at end of file diff --git a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit4Transformer.kt b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit4Transformer.kt new file mode 100644 index 00000000..ddc714c5 --- /dev/null +++ b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit4Transformer.kt @@ -0,0 +1,24 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.junit + +import com.epam.drill.agent.instrument.TransformerObject + +actual object JUnit4Transformer: TransformerObject, AbstractJUnitTransformer() { + override fun permit(className: String, superName: String?, interfaces: Array): Boolean { + return className == "org/junit/runner/notification/RunNotifier" + } +} \ No newline at end of file diff --git a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit5Transformer.kt b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit5Transformer.kt new file mode 100644 index 00000000..d22bebea --- /dev/null +++ b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnit5Transformer.kt @@ -0,0 +1,24 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.junit + +import com.epam.drill.agent.instrument.TransformerObject + +actual object JUnit5Transformer: TransformerObject, AbstractJUnitTransformer() { + override fun permit(className: String, superName: String?, interfaces: Array): Boolean { + return className == "org/junit/platform/engine/support/hierarchical/NodeTestTaskContext" + } +} \ No newline at end of file diff --git a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnitPlatformPrioritizingTransformer.kt b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnitPlatformPrioritizingTransformer.kt new file mode 100644 index 00000000..5856def1 --- /dev/null +++ b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/junit/JUnitPlatformPrioritizingTransformer.kt @@ -0,0 +1,28 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.junit + +import com.epam.drill.agent.configuration.Configuration +import com.epam.drill.agent.configuration.ParameterDefinitions +import com.epam.drill.agent.instrument.TransformerObject + +actual object JUnitPlatformPrioritizingTransformer: TransformerObject, AbstractJUnitTransformer() { + override fun enabled(): Boolean = super.enabled() && Configuration.parameters[ParameterDefinitions.RECOMMENDED_TESTS_ENABLED] + + override fun permit(className: String, superName: String?, interfaces: Array): Boolean { + return className == "org/junit/platform/launcher/core/DefaultLauncher" + } +} \ No newline at end of file diff --git a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/selenium/SeleniumTransformer.kt b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/selenium/SeleniumTransformer.kt new file mode 100644 index 00000000..b884c074 --- /dev/null +++ b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/selenium/SeleniumTransformer.kt @@ -0,0 +1,29 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.selenium + +import com.epam.drill.agent.configuration.Configuration +import com.epam.drill.agent.instrument.InstrumentationParameterDefinitions.INSTRUMENTATION_SELENIUM_ENABLED +import com.epam.drill.agent.instrument.TransformerObject +import com.epam.drill.agent.test.instrument.AbstractTestTransformerObject + +actual object SeleniumTransformer: TransformerObject, AbstractTestTransformerObject() { + override fun enabled() = super.enabled() && Configuration.parameters[INSTRUMENTATION_SELENIUM_ENABLED] + + override fun permit(className: String, superName: String?, interfaces: Array): Boolean { + return className == "org/openqa/selenium/remote/RemoteWebDriver" + } +} \ No newline at end of file diff --git a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/testng/AbstractTestNGPrioritizingTransformer.kt b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/testng/AbstractTestNGPrioritizingTransformer.kt new file mode 100644 index 00000000..908681f4 --- /dev/null +++ b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/testng/AbstractTestNGPrioritizingTransformer.kt @@ -0,0 +1,27 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.testng + +import com.epam.drill.agent.configuration.Configuration +import com.epam.drill.agent.configuration.ParameterDefinitions + +abstract class AbstractTestNGPrioritizingTransformer(): AbstractTestNGTransformer() { + override fun enabled(): Boolean = super.enabled() && Configuration.parameters[ParameterDefinitions.RECOMMENDED_TESTS_ENABLED] + + override fun permit(className: String, superName: String?, interfaces: Array): Boolean { + return interfaces.any { it == "org/testng/IMethodSelector" } + } +} \ No newline at end of file diff --git a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/testng/AbstractTestNGTransformer.kt b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/testng/AbstractTestNGTransformer.kt new file mode 100644 index 00000000..cd448e7d --- /dev/null +++ b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/testng/AbstractTestNGTransformer.kt @@ -0,0 +1,28 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.testng + +import com.epam.drill.agent.configuration.Configuration +import com.epam.drill.agent.instrument.InstrumentationParameterDefinitions.INSTRUMENTATION_TESTNG_ENABLED +import com.epam.drill.agent.test.instrument.AbstractTestTransformerObject + +abstract class AbstractTestNGTransformer(): AbstractTestTransformerObject() { + override fun enabled() = super.enabled() && Configuration.parameters[INSTRUMENTATION_TESTNG_ENABLED] + + override fun permit(className: String, superName: String?, interfaces: Array): Boolean { + return className == "org/testng/TestRunner" + } +} \ No newline at end of file diff --git a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG6PrioritizingTransformer.kt b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG6PrioritizingTransformer.kt new file mode 100644 index 00000000..6fbb7713 --- /dev/null +++ b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG6PrioritizingTransformer.kt @@ -0,0 +1,20 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.testng + +import com.epam.drill.agent.instrument.TransformerObject + +actual object TestNG6PrioritizingTransformer: TransformerObject, AbstractTestNGPrioritizingTransformer() \ No newline at end of file diff --git a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG6Transformer.kt b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG6Transformer.kt new file mode 100644 index 00000000..2d41ad5f --- /dev/null +++ b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG6Transformer.kt @@ -0,0 +1,20 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.testng + +import com.epam.drill.agent.instrument.TransformerObject + +actual object TestNG6Transformer: TransformerObject, AbstractTestNGTransformer() \ No newline at end of file diff --git a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG7PrioritizingTransformer.kt b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG7PrioritizingTransformer.kt new file mode 100644 index 00000000..9e651b7a --- /dev/null +++ b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG7PrioritizingTransformer.kt @@ -0,0 +1,20 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.testng + +import com.epam.drill.agent.instrument.TransformerObject + +actual object TestNG7PrioritizingTransformer: TransformerObject, AbstractTestNGPrioritizingTransformer() \ No newline at end of file diff --git a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG7Transformer.kt b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG7Transformer.kt new file mode 100644 index 00000000..70d59f56 --- /dev/null +++ b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/instrument/testng/TestNG7Transformer.kt @@ -0,0 +1,20 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.instrument.testng + +import com.epam.drill.agent.instrument.TransformerObject + +actual object TestNG7Transformer: TransformerObject, AbstractTestNGTransformer() \ No newline at end of file diff --git a/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/session/SessionController.kt b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/session/SessionController.kt new file mode 100644 index 00000000..852b934e --- /dev/null +++ b/java-agent/src/nativeMain/kotlin/com/epam/drill/agent/test/session/SessionController.kt @@ -0,0 +1,26 @@ +/** + * Copyright 2020 - 2022 EPAM Systems + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.epam.drill.agent.test.session + +import com.epam.drill.agent.jvmapi.callObjectVoidMethod +import com.epam.drill.agent.jvmapi.callObjectVoidMethodWithString + +actual object SessionController { + + actual fun startSession(): Unit = + callObjectVoidMethod(SessionController::class, SessionController::startSession) + +} diff --git a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/ProbesProvider.kt b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/ProbesProvider.kt index 29122602..8342b0fc 100644 --- a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/ProbesProvider.kt +++ b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/ProbesProvider.kt @@ -28,11 +28,15 @@ const val TEST_CONTEXT_NONE = "TEST_CONTEXT_NONE" const val SESSION_CONTEXT_AMBIENT = "GLOBAL" data class ContextKey( - private val _sessionId: SessionId? = null, // TODO there must be a better way to avoid nullability and assign defaults - private val _testId: TestId? = null + var sessionId: SessionId, + var testId: TestId ) { - val sessionId: SessionId = _sessionId ?: SESSION_CONTEXT_NONE - val testId: TestId = _testId ?: TEST_CONTEXT_NONE + fun clear() { + sessionId = SESSION_CONTEXT_NONE + testId = TEST_CONTEXT_NONE + } + fun isSessionEmpty() = sessionId == SESSION_CONTEXT_NONE + fun isSessionGlobal() = sessionId == SESSION_CONTEXT_AMBIENT } -internal val CONTEXT_AMBIENT = ContextKey(SESSION_CONTEXT_AMBIENT) \ No newline at end of file +internal val CONTEXT_AMBIENT = ContextKey(SESSION_CONTEXT_AMBIENT, TEST_CONTEXT_NONE) \ No newline at end of file diff --git a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/ThreadCoverageRecorder.kt b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/ThreadCoverageRecorder.kt index 85e982b7..28be648c 100644 --- a/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/ThreadCoverageRecorder.kt +++ b/test2code/src/main/kotlin/com/epam/drill/agent/test2code/coverage/ThreadCoverageRecorder.kt @@ -28,8 +28,8 @@ class ThreadCoverageRecorder( private val execData: ThreadLocal = TransmittableThreadLocal() override fun startRecording(sessionId: String?, testId: String?) { - stopRecording(context.get()?.sessionId, context.get()?.testId) - val ctx = ContextKey(sessionId, testId) + stopRecording(context.get()?.sessionId ?: SESSION_CONTEXT_NONE, context.get()?.testId ?: TEST_CONTEXT_NONE) + val ctx = ContextKey(sessionId ?: SESSION_CONTEXT_NONE, testId ?: TEST_CONTEXT_NONE) context.set(ctx) execData.set(execDataPool.getOrPut( ctx, @@ -39,9 +39,11 @@ class ThreadCoverageRecorder( } override fun stopRecording(sessionId: String?, testId: String?) { - if (execData.get() == null) return - execDataPool.release(ContextKey(sessionId, testId), execData.get() ?: ExecData()) - execData.remove() + execData.get()?.let { data -> + execDataPool.release(ContextKey(sessionId ?: SESSION_CONTEXT_NONE, testId ?: TEST_CONTEXT_NONE), data) + execData.remove() + } + context.get()?.clear() context.remove() logger.trace { "Test recording stopped (sessionId = $sessionId, testId = $testId, threadId = ${Thread.currentThread().id})." } } @@ -58,7 +60,7 @@ class ThreadCoverageRecorder( } override fun getContext(): ContextCoverage? { - return context.get()?.let { ctx -> execData.get()?.let { ContextCoverage(ctx, it) } } + return context.get()?.takeIf { !it.isSessionEmpty() }?.let { ctx -> execData.get()?.let { ContextCoverage(ctx, it) } } } }