Skip to content

Commit

Permalink
Merge pull request #126 from codellm-devkit/125-analyze-test-classes-…
Browse files Browse the repository at this point in the history
…optionally-if-the-user-wants-to

Feature Request Issue 125: Analyze test classes optionally if the user wants to
  • Loading branch information
rahlk authored Feb 19, 2025
2 parents 5d6be16 + 00da8cd commit 6cb594b
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 42 deletions.
66 changes: 43 additions & 23 deletions src/main/java/com/ibm/cldk/CodeAnalyzer.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class VersionProvider implements CommandLine.IVersionProvider {

public String[] getVersion() throws Exception {
String version = getClass().getPackage().getImplementationVersion();
return new String[]{version != null ? version : "unknown"};
return new String[] { version != null ? version : "unknown" };
}
}

Expand All @@ -56,34 +56,43 @@ public String[] getVersion() throws Exception {
@Command(name = "codeanalyzer", mixinStandardHelpOptions = true, sortOptions = false, versionProvider = VersionProvider.class, description = "Analyze java application.")
public class CodeAnalyzer implements Runnable {

@Option(names = {"-i", "--input"}, description = "Path to the project root directory.")
@Option(names = { "-i", "--input" }, description = "Path to the project root directory.")
private static String input;

@Option(names = {"-t", "--target-files"}, description = "Paths to files to be analyzed from the input application.")
@Option(names = { "-t",
"--target-files" }, description = "Paths to files to be analyzed from the input application.")
private static List<String> targetFiles;

@Option(names = {"-s", "--source-analysis"}, description = "Analyze a single string of java source code instead the project.")
@Option(names = { "-s",
"--source-analysis" }, description = "Analyze a single string of java source code instead the project.")
private static String sourceAnalysis;

@Option(names = {"-o", "--output"}, description = "Destination directory to save the output graphs. By default, the SDG formatted as a JSON will be printed to the console.")
@Option(names = { "-o",
"--output" }, description = "Destination directory to save the output graphs. By default, the SDG formatted as a JSON will be printed to the console.")
private static String output;

@Option(names = {"-b", "--build-cmd"}, description = "Custom build command. Defaults to auto build.")
@Option(names = { "-b", "--build-cmd" }, description = "Custom build command. Defaults to auto build.")
private static String build;

@Option(names = {"--no-build"}, description = "Do not build your application. Use this option if you have already built your application.")
@Option(names = {
"--no-build" }, description = "Do not build your application. Use this option if you have already built your application.")
private static boolean noBuild = false;

@Option(names = {"--no-clean-dependencies"}, description = "Do not attempt to auto-clean dependencies")
@Option(names = { "--no-clean-dependencies" }, description = "Do not attempt to auto-clean dependencies")
public static boolean noCleanDependencies = false;

@Option(names = {"-f", "--project-root-path"}, description = "Path to the root pom.xml/build.gradle file of the project.")
@Option(names = { "-f",
"--project-root-path" }, description = "Path to the root pom.xml/build.gradle file of the project.")
public static String projectRootPom;

@Option(names = {"-a", "--analysis-level"}, description = "Level of analysis to perform. Options: 1 (for just symbol table) or 2 (for call graph). Default: 1")
@Option(names = { "-a",
"--analysis-level" }, description = "Level of analysis to perform. Options: 1 (for just symbol table) or 2 (for call graph). Default: 1")
private static int analysisLevel = 1;

@Option(names = {"-v", "--verbose"}, description = "Print logs to console.")
@Option(names = { "--include-test-classes" }, hidden = true, description = "Print logs to console.")
public static boolean includeTestClasses = false;

@Option(names = { "-v", "--verbose" }, description = "Print logs to console.")
private static boolean verbose = false;

private static final String outputFileName = "analysis.json";
Expand Down Expand Up @@ -121,11 +130,13 @@ private static void analyze() throws Exception {
JsonObject combinedJsonObject = new JsonObject();
Map<String, JavaCompilationUnit> symbolTable;
projectRootPom = projectRootPom == null ? input : projectRootPom;
// First of all if, sourceAnalysis is provided, we will analyze the source code instead of the project.
// First of all if, sourceAnalysis is provided, we will analyze the source code
// instead of the project.
if (sourceAnalysis != null) {
// Construct symbol table for source code
Log.debug("Single file analysis.");
Pair<Map<String, JavaCompilationUnit>, Map<String, List<Problem>>> symbolTableExtractionResult = SymbolTable.extractSingle(sourceAnalysis);
Pair<Map<String, JavaCompilationUnit>, Map<String, List<Problem>>> symbolTableExtractionResult = SymbolTable
.extractSingle(sourceAnalysis);
symbolTable = symbolTableExtractionResult.getLeft();
} else {
// download library dependencies of project for type resolution
Expand All @@ -140,9 +151,11 @@ private static void analyze() throws Exception {
Log.warn("Failed to download library dependencies of project");
}

boolean analysisFileExists = output != null && Files.exists(Paths.get(output + File.separator + outputFileName));
boolean analysisFileExists = output != null
&& Files.exists(Paths.get(output + File.separator + outputFileName));

// if target files are specified, compute symbol table information for the given files
// if target files are specified, compute symbol table information for the given
// files
if (targetFiles != null) {
Log.info(targetFiles.size() + "target files specified for analysis: " + targetFiles);

Expand All @@ -154,17 +167,22 @@ private static void analyze() throws Exception {
}

// Previous code was pointing to toList, which has been introduced in Java 16
// symbolTable = SymbolTable.extract(Paths.get(input), targetFiles.stream().map(Paths::get).toList()).getLeft();
// symbolTable = SymbolTable.extract(Paths.get(input),
// targetFiles.stream().map(Paths::get).toList()).getLeft();
// extract symbol table for the specified files
symbolTable = SymbolTable.extract(Paths.get(input), targetFiles.stream().map(Paths::get).collect(Collectors.toList())).getLeft();
symbolTable = SymbolTable
.extract(Paths.get(input), targetFiles.stream().map(Paths::get).collect(Collectors.toList()))
.getLeft();

// if analysis file exists, update it with new symbol table information for the specified fiels
// if analysis file exists, update it with new symbol table information for the
// specified fiels
if (analysisFileExists) {
// read symbol table information from existing analysis file
Map<String, JavaCompilationUnit> existingSymbolTable = readSymbolTableFromFile(
new File(output, outputFileName));
if (existingSymbolTable != null) {
// for each file, tag its symbol table information as "updated" and update existing symbol table
// for each file, tag its symbol table information as "updated" and update
// existing symbol table
for (String targetFile : targetFiles) {
String targetPathAbs = Paths.get(targetFile).toAbsolutePath().toString();
JavaCompilationUnit javaCompilationUnit = symbolTable.get(targetPathAbs);
Expand All @@ -175,16 +193,18 @@ private static void analyze() throws Exception {
symbolTable = existingSymbolTable;
}
} else {
// construct symbol table for project, write parse problems to file in output directory if specified
Pair<Map<String, JavaCompilationUnit>, Map<String, List<Problem>>> symbolTableExtractionResult
= SymbolTable.extractAll(Paths.get(input));
// construct symbol table for project, write parse problems to file in output
// directory if specified
Pair<Map<String, JavaCompilationUnit>, Map<String, List<Problem>>> symbolTableExtractionResult = SymbolTable
.extractAll(Paths.get(input));

symbolTable = symbolTableExtractionResult.getLeft();
}

if (analysisLevel > 1) {
// Save SDG, and Call graph as JSON
// If noBuild is not true, and build is also not provided, we will use "auto" as the build command
// If noBuild is not true, and build is also not provided, we will use "auto" as
// the build command
build = build == null ? "auto" : build;
// Is noBuild is true, we will not build the project
build = noBuild ? null : build;
Expand Down
19 changes: 15 additions & 4 deletions src/main/java/com/ibm/cldk/utils/BuildProject.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package com.ibm.cldk.utils;

import com.ibm.cldk.CodeAnalyzer;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
Expand All @@ -17,6 +15,7 @@
import static com.ibm.cldk.utils.ProjectDirectoryScanner.classFilesStream;
import static com.ibm.cldk.CodeAnalyzer.projectRootPom;
import static com.ibm.cldk.CodeAnalyzer.noCleanDependencies;
import static com.ibm.cldk.CodeAnalyzer.includeTestClasses;

public class BuildProject {
public static Path libDownloadPath;
Expand Down Expand Up @@ -140,7 +139,14 @@ private static boolean mavenBuild(String projectPath) {
Log.info("Checking if Maven is installed.");
return false;
}
String[] mavenCommand = {MAVEN_CMD, "clean", "compile", "-f", projectPath + "/pom.xml", "-B", "-V", "-e", "-Drat.skip", "-Dfindbugs.skip", "-Dcheckstyle.skip", "-Dpmd.skip=true", "-Dspotbugs.skip", "-Denforcer.skip", "-Dmaven.javadoc.skip", "-DskipTests", "-Dmaven.test.skip.exec", "-Dlicense.skip=true", "-Drat.skip=true", "-Dspotless.check.skip=true"};

String[] mavenCommand;
if (includeTestClasses) {
Log.warn("Hidden flag `--include-test-classes` is turned on. We'll including test classes in WALA analysis");
mavenCommand = new String[]{MAVEN_CMD, "clean", "test-compile", "-f", projectPath + "/pom.xml", "-B", "-V", "-e", "-Drat.skip", "-Dfindbugs.skip", "-Dcheckstyle.skip", "-Dpmd.skip=true", "-Dspotbugs.skip", "-Denforcer.skip", "-Dmaven.javadoc.skip", "-DskipTests", "-Dmaven.test.skip.exec", "-Dlicense.skip=true", "-Drat.skip=true", "-Dspotless.check.skip=true"};
}
else
mavenCommand = new String[]{MAVEN_CMD, "clean", "compile", "-f", projectPath + "/pom.xml", "-B", "-V", "-e", "-Drat.skip", "-Dfindbugs.skip", "-Dcheckstyle.skip", "-Dpmd.skip=true", "-Dspotbugs.skip", "-Denforcer.skip", "-Dmaven.javadoc.skip", "-DskipTests", "-Dmaven.test.skip.exec", "-Dlicense.skip=true", "-Drat.skip=true", "-Dspotless.check.skip=true"};

return buildWithTool(mavenCommand);
}
Expand All @@ -151,7 +157,12 @@ public static boolean gradleBuild(String projectPath) {
if (GRADLE_CMD.equals("gradlew") || GRADLE_CMD.equals("gradlew.bat")) {
gradleCommand = new String[]{projectPath + File.separator + GRADLE_CMD, "clean", "compileJava", "-p", projectPath};
} else {
gradleCommand = new String[]{GRADLE_CMD, "clean", "compileJava", "-p", projectPath};
if (includeTestClasses) {
Log.warn("Hidden flag `--include-test-classes` is turned on. We'll including test classes in WALA analysis");
gradleCommand = new String[]{GRADLE_CMD, "clean", "compileTestJava", "-p", projectPath};
}
else
gradleCommand = new String[]{GRADLE_CMD, "clean", "compileJava", "-p", projectPath};
}
return buildWithTool(gradleCommand);
}
Expand Down
25 changes: 10 additions & 15 deletions src/main/java/com/ibm/cldk/utils/ProjectDirectoryScanner.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,15 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.ibm.cldk.CodeAnalyzer.includeTestClasses;

public class ProjectDirectoryScanner {
public static List<Path> classFilesStream(String projectPath) throws IOException {
Path projectDir = Paths.get(projectPath).toAbsolutePath();
Log.info("Finding *.class files in " + projectDir);
if (Files.exists(projectDir)) {
try (Stream<Path> paths = Files.walk(projectDir)) {
return paths.filter(file -> !Files.isDirectory(file) && file.toString().endsWith(".class"))
.filter(file -> {
// Let's find the path relative to the project directory
Path relativePath = projectDir.relativize(file.toAbsolutePath());
String relativePathAsString = relativePath.toString().replace("\\", "/"); // Windows fix
return !relativePathAsString.contains("test/resources/") && !relativePathAsString.contains("main/resources/");
})
.collect(Collectors.toList());
return paths.filter(file -> !Files.isDirectory(file) && file.toString().endsWith(".class")).collect(Collectors.toList());
}
}
return null;
Expand All @@ -47,13 +42,13 @@ public static List<Path> sourceFilesStream(String projectPath) throws IOExceptio
if (Files.exists(projectDir)) {
try (Stream<Path> paths = Files.walk(projectDir)) {
return paths
.filter(file -> !Files.isDirectory(file))
.filter(file -> file.toString().endsWith(".java"))
.filter(file -> !file.toAbsolutePath().toString().contains("build/"))
.filter(file -> !file.toAbsolutePath().toString().contains("target/"))
.filter(file -> !file.toAbsolutePath().toString().contains("main/resources/"))
.filter(file -> !file.toAbsolutePath().toString().contains("test/resources/"))
.collect(Collectors.toList());
.filter(file -> !Files.isDirectory(file))
.filter(file -> file.toString().endsWith(".java"))
.filter(file -> !file.toAbsolutePath().toString().contains("build/"))
.filter(file -> !file.toAbsolutePath().toString().contains("target/"))
.filter(file -> !file.toAbsolutePath().toString().contains("main/resources/"))
.filter(file -> !file.toAbsolutePath().toString().contains("test/resources/"))
.collect(Collectors.toList());
}
}
return null;
Expand Down

0 comments on commit 6cb594b

Please sign in to comment.