Skip to content

Commit

Permalink
refactor: init code smell for testing
Browse files Browse the repository at this point in the history
  • Loading branch information
phodal committed Dec 4, 2023
1 parent 8990cee commit c6385f2
Show file tree
Hide file tree
Showing 4 changed files with 267 additions and 0 deletions.
15 changes: 15 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ clikt = "4.2.1"

chocolate-factory="0.4.3"

chapi = "2.1.3"
archguard="2.0.7"

[plugins]
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
Expand Down Expand Up @@ -46,6 +49,18 @@ test-kotlintest-assertions = { module = "io.kotest:kotest-assertions-core", vers
test-mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
test-assertj = { group = "org.assertj", name = "assertj-core", version.ref = "assertj" }

# chapi
chapi-domain = { group = "com.phodal.chapi", name = "chapi-domain", version.ref = "chapi" }
chapi-java = { group = "com.phodal.chapi", name = "chapi-ast-java", version.ref = "chapi" }
chapi-kotlin = { group = "com.phodal.chapi", name = "chapi-ast-kotlin", version.ref = "chapi" }


# ArchGurad
archguard-scanner-core = { group = "org.archguard.scanner", name = "scanner_core", version.ref = "archguard" }
archguard-lang-kotlin = { group = "org.archguard.scanner", name = "lang_kotlin", version.ref = "archguard" }
archguard-rule-core = { group = "org.archguard.scanner", name = "rule_core", version.ref = "archguard" }
archguard-analyser-estimate = { group = "org.archguard.scanner", name = "analyser_estimate", version.ref = "archguard" }

# cf => chocolate-factory
cf-language = { group = "cc.unitmesh", name = "code-language", version.ref = "chocolate-factory" }

Expand Down
6 changes: 6 additions & 0 deletions unit-picker/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ dependencies {
implementation(libs.clikt)
implementation(libs.serialization.json)

implementation(libs.chapi.domain)
implementation(libs.chapi.java)
implementation(libs.chapi.kotlin)

implementation(libs.archguard.analyser.estimate)

// Logging
implementation(libs.logging.slf4j.api)
implementation(libs.logging.logback.classic)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.unimesh.eval.picker.bs

import chapi.domain.core.CodeDataStruct

class BadsmellChecker(val data: List<CodeDataStruct>) {
fun check() {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
package org.unimesh.eval.picker.bs

import chapi.domain.core.CodeAnnotation
import chapi.domain.core.CodeCall
import chapi.domain.core.CodeDataStruct
import chapi.domain.core.CodeFunction
import kotlinx.serialization.Serializable

@Serializable
data class TestBadSmell(
var FileName: String = "",
var Type: String = "",
var Description: String = "",
var Line: Int = 0
)
data class TbsResult(var results: Array<TestBadSmell>)

class TbsAnalyser(val nodes: List<CodeDataStruct>) {
fun analysisByPath(path: String): Array<TestBadSmell> {
val tbsResult: TbsResult = TbsResult(arrayOf())
val callMethodMap = buildCallMethodMap(nodes)

for (node in nodes) {
for (method in node.Functions) {
if (!method.isJUnitTest()) {
continue
}

val currentMethodCalls = addExtractAssertMethodCall(method, node, callMethodMap)
method.FunctionCalls = currentMethodCalls

for (annotation in method.Annotations) {
checkIgnoreTest(node.FilePath, annotation, tbsResult, method)
checkEmptyTest(node.FilePath, annotation, tbsResult, method)
}

val methodCallMap = hashMapOf<String, Array<CodeCall>>()
var hasAssert = false

for ((index, funcCall) in currentMethodCalls.withIndex()) {
if (funcCall.FunctionName == "") {
val lastFuncCall = index == currentMethodCalls.size - 1
if (lastFuncCall && !hasAssert) {
appendUnknownTest(node.FilePath, method, tbsResult)
}
continue
}

updateMethodCallMap(funcCall, methodCallMap)

checkRedundantPrintTest(node.FilePath, funcCall, tbsResult)
checkSleepyTest(node.FilePath, funcCall, tbsResult)
checkRedundantAssertionTest(node.FilePath, funcCall, tbsResult)

if (funcCall.hasAssertion()) hasAssert = true

val lastFuncCall = index == currentMethodCalls.size - 1
if (lastFuncCall && !hasAssert) {
appendUnknownTest(node.FilePath, method, tbsResult)
}
}

checkDuplicateAssertTest(node, method, methodCallMap, tbsResult)
}
}

return tbsResult.results
}

private fun addExtractAssertMethodCall(
method: CodeFunction,
node: CodeDataStruct,
callMethodMap: MutableMap<String, CodeFunction>
): List<CodeCall> {
var methodCalls = method.FunctionCalls
for (methodCall in methodCalls) {
if (methodCall.NodeName == node.NodeName) {
val mapFunc = callMethodMap[methodCall.buildFullMethodName()]
if (mapFunc != null && mapFunc.Name != "") {
methodCalls += mapFunc.FunctionCalls
}
}
}

return methodCalls
}

private fun updateMethodCallMap(
funcCall: CodeCall,
methodCallMap: HashMap<String, Array<CodeCall>>
) {
var calls: Array<CodeCall> = arrayOf()
val buildFullMethodName = funcCall.buildFullMethodName()
if (methodCallMap[buildFullMethodName] != null) {
calls = methodCallMap[buildFullMethodName]!!
}
calls += funcCall
methodCallMap[buildFullMethodName] = calls
}

private fun checkDuplicateAssertTest(
node: CodeDataStruct,
method: CodeFunction,
methodCallMap: MutableMap<String, Array<CodeCall>>,
tbsResult: TbsResult
) {
var isDuplicateTest = false
for (entry in methodCallMap) {
val methodCalls = entry.value
val duplicatedLimitLength = 5
if (methodCalls.size >= duplicatedLimitLength) {
if (methodCalls.last().hasAssertion()) {
isDuplicateTest = true
}
}
}

if (isDuplicateTest) {
val testBadSmell = TestBadSmell(
FileName = node.FilePath,
Type = "DuplicateAssertTest",
Description = "",
Line = method.Position.StartLine
)

tbsResult.results += testBadSmell
}
}

private fun appendUnknownTest(filePath: String, method: CodeFunction, tbsResult: TbsResult) {
val testBadSmell = TestBadSmell(
FileName = filePath,
Type = "UnknownTest",
Description = "",
Line = method.Position.StartLine
)

tbsResult.results += testBadSmell
}

private fun checkRedundantAssertionTest(
filePath: String,
funcCall: CodeCall,
tbsResult: TbsResult
) {
val assertParametersSize = 2
if (funcCall.Parameters.size == assertParametersSize) {
if (funcCall.Parameters[0].TypeValue == funcCall.Parameters[1].TypeValue) {
val testBadSmell = TestBadSmell(
FileName = filePath,
Type = "RedundantAssertionTest",
Description = "",
Line = funcCall.Position.StartLine
)

tbsResult.results += testBadSmell
}
}
}

private fun checkSleepyTest(filePath: String, funcCall: CodeCall, tbsResult: TbsResult) {
if (funcCall.isThreadSleep()) {
val testBadSmell = TestBadSmell(
FileName = filePath,
Type = "SleepyTest",
Description = "",
Line = funcCall.Position.StartLine
)

tbsResult.results += testBadSmell
}
}

private fun checkRedundantPrintTest(filePath: String, funcCall: CodeCall, tbsResult: TbsResult) {
if (funcCall.isSystemOutput()) {
val testBadSmell = TestBadSmell(
FileName = filePath,
Type = "RedundantPrintTest",
Description = "",
Line = funcCall.Position.StartLine
)

tbsResult.results += testBadSmell
}
}

private fun checkIgnoreTest(
filePath: String,
annotation: CodeAnnotation,
tbsResult: TbsResult,
method: CodeFunction
) {
if (annotation.isIgnore()) {
val testBadSmell = TestBadSmell(
FileName = filePath,
Type = "IgnoreTest",
Description = "",
Line = method.Position.StartLine
)

tbsResult.results += testBadSmell
}
}

private fun checkEmptyTest(
filePath: String,
annotation: CodeAnnotation,
tbsResult: TbsResult,
method: CodeFunction
) {
val isJavaTest = filePath.endsWith(".java") && annotation.isTest()
val isGoTest = filePath.endsWith("_test.go")
if (isJavaTest || isGoTest) {
if (method.FunctionCalls.size <= 1) {
val badSmell = TestBadSmell(
FileName = filePath,
Type = "EmptyTest",
Description = "",
Line = method.Position.StartLine
)

tbsResult.results += badSmell
}
}
}

private fun buildCallMethodMap(nodes: List<CodeDataStruct>): MutableMap<String, CodeFunction> {
val callMethodMap: MutableMap<String, CodeFunction> = mutableMapOf()
for (node in nodes) {
for (method in node.Functions) {
callMethodMap[method.buildFullMethodName(node)] = method
}
}

return callMethodMap
}
}

0 comments on commit c6385f2

Please sign in to comment.