Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update JavaParser version and fix configured smells being ignored #26

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@
<dependency>
<groupId>com.github.javaparser</groupId>
<artifactId>javaparser-core</artifactId>
<version>3.2.4</version>
<version>3.24.4</version>
</dependency>
<dependency>
<groupId>com.opencsv</groupId>
Expand Down Expand Up @@ -162,4 +162,4 @@
</dependency>
</dependencies>

</project>
</project>
8 changes: 8 additions & 0 deletions src/main/java/testsmell/SmellFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package testsmell;

import thresholds.Thresholds;

@FunctionalInterface
public interface SmellFactory {
AbstractSmell createInstance(Thresholds thresholds);
}
60 changes: 31 additions & 29 deletions src/main/java/testsmell/TestSmellDetector.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package testsmell;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.body.TypeDeclaration;
import org.apache.commons.lang3.StringUtils;
Expand All @@ -16,7 +15,7 @@

public class TestSmellDetector {

private List<AbstractSmell> testSmells;
private List<SmellFactory> testSmells;
private Thresholds thresholds;

/**
Expand All @@ -32,30 +31,30 @@ public TestSmellDetector(Thresholds thresholds) {

private void initializeSmells() {
testSmells = new ArrayList<>();
testSmells.add(new AssertionRoulette(thresholds));
testSmells.add(new ConditionalTestLogic(thresholds));
testSmells.add(new ConstructorInitialization(thresholds));
testSmells.add(new DefaultTest(thresholds));
testSmells.add(new EmptyTest(thresholds));
testSmells.add(new ExceptionCatchingThrowing(thresholds));
testSmells.add(new GeneralFixture(thresholds));
testSmells.add(new MysteryGuest(thresholds));
testSmells.add(new PrintStatement(thresholds));
testSmells.add(new RedundantAssertion(thresholds));
testSmells.add(new SensitiveEquality(thresholds));
testSmells.add(new VerboseTest(thresholds));
testSmells.add(new SleepyTest(thresholds));
testSmells.add(new EagerTest(thresholds));
testSmells.add(new LazyTest(thresholds));
testSmells.add(new DuplicateAssert(thresholds));
testSmells.add(new UnknownTest(thresholds));
testSmells.add(new IgnoredTest(thresholds));
testSmells.add(new ResourceOptimism(thresholds));
testSmells.add(new MagicNumberTest(thresholds));
testSmells.add(new DependentTest(thresholds));
testSmells.add(AssertionRoulette::new);
testSmells.add(ConditionalTestLogic::new);
testSmells.add(ConstructorInitialization::new);
testSmells.add(DefaultTest::new);
testSmells.add(EmptyTest::new);
testSmells.add(ExceptionCatchingThrowing::new);
testSmells.add(GeneralFixture::new);
testSmells.add(MysteryGuest::new);
testSmells.add(PrintStatement::new);
testSmells.add(RedundantAssertion::new);
testSmells.add(SensitiveEquality::new);
testSmells.add(VerboseTest::new);
testSmells.add(SleepyTest::new);
testSmells.add(EagerTest::new);
testSmells.add(LazyTest::new);
testSmells.add(DuplicateAssert::new);
testSmells.add(UnknownTest::new);
testSmells.add(IgnoredTest::new);
testSmells.add(ResourceOptimism::new);
testSmells.add(MagicNumberTest::new);
testSmells.add(DependentTest::new);
}

public void setTestSmells(List<AbstractSmell> testSmells) {
public void setTestSmells(List<SmellFactory> testSmells) {
this.testSmells = testSmells;
}

Expand All @@ -65,32 +64,35 @@ public void setTestSmells(List<AbstractSmell> testSmells) {
* @return list of smell names
*/
public List<String> getTestSmellNames() {
return testSmells.stream().map(AbstractSmell::getSmellName).collect(Collectors.toList());
return testSmells.stream()
.map(factory -> factory.createInstance(thresholds))
.map(AbstractSmell::getSmellName)
.collect(Collectors.toList());
}

/**
* Loads the java source code file into an AST and then analyzes it for the existence of the different types of
* test smells
*/
public TestFile detectSmells(TestFile testFile) throws IOException {
initializeSmells();
CompilationUnit testFileCompilationUnit = null;
CompilationUnit productionFileCompilationUnit = null;
FileInputStream testFileInputStream, productionFileInputStream;

if (!StringUtils.isEmpty(testFile.getTestFilePath())) {
testFileInputStream = new FileInputStream(testFile.getTestFilePath());
testFileCompilationUnit = JavaParser.parse(testFileInputStream);
testFileCompilationUnit = Util.parseJava(testFileInputStream);
TypeDeclaration typeDeclaration = testFileCompilationUnit.getTypes().get(0);
testFile.setNumberOfTestMethods(typeDeclaration.getMethods().size());
}

if (!StringUtils.isEmpty(testFile.getProductionFilePath())) {
productionFileInputStream = new FileInputStream(testFile.getProductionFilePath());
productionFileCompilationUnit = JavaParser.parse(productionFileInputStream);
productionFileCompilationUnit = Util.parseJava(productionFileInputStream);
}

for (AbstractSmell smell : testSmells) {
for (SmellFactory smellFactory : testSmells) {
AbstractSmell smell = smellFactory.createInstance(thresholds);
try {
smell.runAnalysis(testFileCompilationUnit, productionFileCompilationUnit,
testFile.getTestFileNameWithoutExtension(),
Expand Down
35 changes: 33 additions & 2 deletions src/main/java/testsmell/Util.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
package testsmell;

import java.io.ByteArrayInputStream;
import java.io.InputStream;

import com.github.javaparser.JavaParser;
import com.github.javaparser.ParserConfiguration;
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.Modifier;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ParseProblemException;
import com.github.javaparser.ParseResult;
import com.github.javaparser.ast.CompilationUnit;

public class Util {

Expand All @@ -12,7 +21,7 @@ public static boolean isValidTestMethod(MethodDeclaration n) {
//only analyze methods that either have a @test annotation (Junit 4) or the method name starts with 'test'
if (n.getAnnotationByName("Test").isPresent() || n.getNameAsString().toLowerCase().startsWith("test")) {
//must be a public method
if (n.getModifiers().contains(Modifier.PUBLIC)) {
if (n.getModifiers().stream().anyMatch(m -> m.getKeyword() == Modifier.Keyword.PUBLIC)) {
valid = true;
}
}
Expand All @@ -28,7 +37,7 @@ public static boolean isValidSetupMethod(MethodDeclaration n) {
//only analyze methods that either have a @Before annotation (Junit 4) or the method name is 'setUp'
if (n.getAnnotationByName("Before").isPresent() || n.getNameAsString().equals("setUp")) {
//must be a public method
if (n.getModifiers().contains(Modifier.PUBLIC)) {
if (n.getModifiers().stream().anyMatch(m -> m.getKeyword() == Modifier.Keyword.PUBLIC)) {
valid = true;
}
}
Expand All @@ -54,4 +63,26 @@ public static boolean isNumber(String str) {
}
return false;
}

/**
* Replicates the old {@link JavaParser#parse(InputStream)} behavior without {@link StaticJavaParser},
* since other projects may mess with the static configuration.
*/
public static CompilationUnit parseJava(InputStream code) {
JavaParser parser = new JavaParser();
parser.getParserConfiguration()
.setLanguageLevel(ParserConfiguration.LanguageLevel.BLEEDING_EDGE) // use latest supported java version
.setAttributeComments(false); // exclude comments

ParseResult<CompilationUnit> parseResult = parser.parse(code);
if (parseResult.isSuccessful() && parseResult.getResult().isPresent()) {
return parseResult.getResult().get();
} else {
throw new ParseProblemException(parseResult.getProblems());
}
}

public static CompilationUnit parseJava(String code) {
return parseJava(new ByteArrayInputStream(code.getBytes()));
}
}
2 changes: 1 addition & 1 deletion src/main/java/testsmell/smell/ConditionalTestLogic.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ public void visit(ForStmt n, Void arg) {
}

@Override
public void visit(ForeachStmt n, Void arg) {
public void visit(ForEachStmt n, Void arg) {
super.visit(n, arg);
if (currentMethod != null) {
foreachCount++;
Expand Down
10 changes: 9 additions & 1 deletion src/main/java/testsmell/smell/DependentTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

public class DependentTest extends AbstractSmell {

Expand Down Expand Up @@ -38,8 +40,14 @@ public void runAnalysis(CompilationUnit testFileCompilationUnit, CompilationUnit
classVisitor = new DependentTest.ClassVisitor();
classVisitor.visit(testFileCompilationUnit, null);

Set<String> testMethodNames = testMethods.stream()
.map(method -> method.getMethodDeclaration().getNameAsString())
.collect(Collectors.toSet());

for (TestMethod testMethod : testMethods) {
if (testMethod.getCalledMethods().stream().anyMatch(x -> x.getName().equals(testMethods.stream().map(z -> z.getMethodDeclaration().getNameAsString())))) {
boolean match = testMethod.getCalledMethods()
.stream().anyMatch(method -> testMethodNames.contains(method.getName()));
if (match) {
smellyElementsSet.add(new testsmell.TestMethod(testMethod.getMethodDeclaration().getNameAsString()));
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/testsmell/smell/EagerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public void visit(MethodDeclaration n, Void arg) {
}
} else { //collect a list of all public/protected members of the production class
for (Modifier modifier : n.getModifiers()) {
if (modifier.name().toLowerCase().equals("public") || modifier.name().toLowerCase().equals("protected")) {
if (modifier.getKeyword() == Modifier.Keyword.PUBLIC || modifier.getKeyword() == Modifier.Keyword.PROTECTED) {
productionMethods.add(n);
}
}
Expand Down Expand Up @@ -220,4 +220,4 @@ public void visit(VariableDeclarator n, Void arg) {
super.visit(n, arg);
}
}
}
}
2 changes: 1 addition & 1 deletion src/main/java/testsmell/smell/IgnoredTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public void visit(MethodDeclaration n, Void arg) {
//JUnit 3
//check if test method is not public
if (n.getNameAsString().toLowerCase().startsWith("test")) {
if (!n.getModifiers().contains(Modifier.PUBLIC)) {
if (n.getModifiers().stream().noneMatch(m -> m.getKeyword() == Modifier.Keyword.PUBLIC)) {
testMethod = new TestMethod(n.getNameAsString());
testMethod.setSmell(true);
smellyElementsSet.add(testMethod);
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/testsmell/smell/LazyTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ public void visit(MethodDeclaration n, Void arg) {
}
} else { //collect a list of all public/protected members of the production class
for (Modifier modifier : n.getModifiers()) {
if (modifier.name().toLowerCase().equals("public") || modifier.name().toLowerCase().equals("protected")) {
if (modifier.getKeyword() == Modifier.Keyword.PUBLIC || modifier.getKeyword() == Modifier.Keyword.PROTECTED) {
productionMethods.add(n);
}
}
Expand Down
13 changes: 7 additions & 6 deletions src/main/java/testsmell/smell/UnknownTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package testsmell.smell;

import com.github.javaparser.ast.CompilationUnit;
import com.github.javaparser.ast.Node;
import com.github.javaparser.ast.NodeList;
import com.github.javaparser.ast.body.MethodDeclaration;
import com.github.javaparser.ast.expr.AnnotationExpr;
Expand Down Expand Up @@ -55,14 +56,14 @@ public void visit(MethodDeclaration n, Void arg) {
if (Util.isValidTestMethod(n)) {
Optional<AnnotationExpr> assertAnnotation = n.getAnnotationByName("Test");
if (assertAnnotation.isPresent()) {
for (int i = 0; i < assertAnnotation.get().getNodeLists().size(); i++) {
NodeList<?> c = assertAnnotation.get().getNodeLists().get(i);
for (int j = 0; j < c.size(); j++)
if (c.get(j) instanceof MemberValuePair) {
if (((MemberValuePair) c.get(j)).getName().equals("expected") && ((MemberValuePair) c.get(j)).getValue().toString().contains("Exception"))
;
for (Node node : assertAnnotation.get().getChildNodes()) {
if (node instanceof MemberValuePair) {
MemberValuePair pair = (MemberValuePair) node;
if (pair.getName().getIdentifier().equals("expected")
&& pair.getValue().toString().contains("Exception")) {
hasExceptionAnnotation = true;
}
}
}
}
currentMethod = n;
Expand Down
8 changes: 4 additions & 4 deletions src/test/kotlin/testsmell/TestAssertionsDetection.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package testsmell

import com.github.javaparser.JavaParser
import com.github.javaparser.StaticJavaParser
import com.github.javaparser.ast.CompilationUnit
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
Expand All @@ -19,8 +19,8 @@ class TestAssertionsDetection {

@BeforeEach
fun setup() {
testCompilationUnit = JavaParser.parse(simpleTest)
productionCompilationUnit = JavaParser.parse(simpleClass)
testCompilationUnit = StaticJavaParser.parse(simpleTest)
productionCompilationUnit = StaticJavaParser.parse(simpleClass)
testFile = mock(TestFile::class.java)
Mockito.`when`(testFile.testFileNameWithoutExtension).thenReturn("fake/path")
Mockito.`when`(testFile.productionFileNameWithoutExtension).thenReturn("fake/path")
Expand Down Expand Up @@ -83,4 +83,4 @@ class TestAssertionsDetection {
}
}
""".trimIndent()
}
}
10 changes: 5 additions & 5 deletions src/test/kotlin/testsmell/TestDetectionCorrectness.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package testsmell

import com.github.javaparser.JavaParser
import com.github.javaparser.ast.CompilationUnit
import org.junit.jupiter.api.Assertions
import org.junit.jupiter.api.BeforeEach
Expand All @@ -10,6 +9,7 @@ import org.mockito.Mockito.`when`
import org.mockito.Mockito.mock
import testsmell.smell.AssertionRoulette
import testsmell.smell.EagerTest
import testsmell.Util;
import thresholds.DefaultThresholds
import thresholds.SpadiniThresholds

Expand All @@ -24,8 +24,8 @@ class TestDetectionCorrectness {

@BeforeEach
fun setup() {
testCompilationUnit = JavaParser.parse(fractionTest)
productionCompilationUnit = JavaParser.parse(fractionSource)
testCompilationUnit = Util.parseJava(fractionTest)
productionCompilationUnit = Util.parseJava(fractionSource)
testFile = mock(TestFile::class.java)
`when`(testFile.testFileNameWithoutExtension).thenReturn("fake/path")
`when`(testFile.productionFileNameWithoutExtension).thenReturn("fake/path")
Expand Down Expand Up @@ -2111,7 +2111,7 @@ class TestDetectionCorrectness {
f = Fraction.getFraction(-1, 1, Integer.MAX_VALUE);
assertEquals("-2147483648/2147483647", f.toString());
}

@Test
public void testToProperString() {
Fraction f;
Expand Down Expand Up @@ -2149,4 +2149,4 @@ class TestDetectionCorrectness {
}
}
""".trimIndent()
}
}