Skip to content

Commit

Permalink
Merge pull request #109 from pengyuejiang/mixed-granularity
Browse files Browse the repository at this point in the history
Add HybridMojo and MethodsMojo
  • Loading branch information
owolabileg authored Oct 18, 2023
2 parents ac33ab8 + e5f82d4 commit 46cb80a
Show file tree
Hide file tree
Showing 2 changed files with 710 additions and 0 deletions.
398 changes: 398 additions & 0 deletions starts-plugin/src/main/java/edu/illinois/starts/jdeps/HybridMojo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,398 @@
/*
* Copyright (c) 2015 - Present. The STARTS Team. All Rights Reserved.
*/

package edu.illinois.starts.jdeps;

import java.io.IOException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;

import edu.illinois.starts.helpers.ZLCHelperMethods;
import edu.illinois.starts.smethods.MethodLevelStaticDepsBuilder;
import edu.illinois.starts.util.ChecksumUtil;
import edu.illinois.starts.util.Logger;

import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.Execute;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.apache.maven.surefire.booter.Classpath;

@Mojo(name = "hybrid", requiresDirectInvocation = true, requiresDependencyResolution = ResolutionScope.TEST)
@Execute(phase = LifecyclePhase.TEST_COMPILE)
public class HybridMojo extends DiffMojo {

private Logger logger;
private Set<String> changedMethods;
private Set<String> newMethods;
private Set<String> impactedMethods;
private Set<String> newClasses;
private Set<String> oldClasses;
private Set<String> affectedTestClasses;
private Set<String> nonAffectedMethods;
private Map<String, String> methodsCheckSum;
private Map<String, List<String>> classesChecksum;
private Map<String, Set<String>> methodToTestClasses;
private ClassLoader loader;
private Map<String, Set<String>> classDependencyGraph;
private Map<String, Set<String>> classToTestClassGraph;
private Set<String> deletedClasses;
private Set<String> changedClassesWithChangedHeaders;
private Set<String> changedClassesWithoutChangedHeaders;
private Set<String> impactedClasses;

@Parameter(property = "computeImpactedMethods", defaultValue = TRUE)
private boolean computeImpactedMethods;

@Parameter(property = "updateMethodsChecksums", defaultValue = TRUE)
private boolean updateMethodsChecksums;

@Parameter(property = "includeVariables", defaultValue = FALSE)
private boolean includeVariables;

@Parameter(property = "debug", defaultValue = TRUE)
private boolean debug;

public void setComputeImpactedMethods(boolean computeImpactedMethods) {
this.computeImpactedMethods = computeImpactedMethods;
}

public void setUpdateMethodsChecksums(boolean updateChecksums) {
this.updateMethodsChecksums = updateChecksums;
}

public Set<String> getAffectedMethods() {
Set<String> affectedMethods = new HashSet<>();
affectedMethods.addAll(changedMethods);
affectedMethods.addAll(newMethods);
return Collections.unmodifiableSet(affectedMethods);
}

public Set<String> getAffectedClasses() {
Set<String> affectedClasses = new HashSet<>();
affectedClasses.addAll(changedClassesWithChangedHeaders);
affectedClasses.addAll(newClasses);
return Collections.unmodifiableSet(affectedClasses);
}

public Set<String> getImpactedMethods() {
return Collections.unmodifiableSet(impactedMethods);
}

public Set<String> getImpactedClasses() {
return Collections.unmodifiableSet(impactedClasses);
}

public Set<String> getNewClasses() {
return Collections.unmodifiableSet(newClasses);
}

public Set<String> getOldClasses() {
return Collections.unmodifiableSet(oldClasses);
}

public Set<String> getChangedClassesWithChangedHeaders() throws MojoExecutionException {
Set<String> changedC = new HashSet<>();
for (String c : changedClassesWithChangedHeaders) {
URL url = loader.getResource(ChecksumUtil.toClassOrJavaName(c, false));
String extForm = url.toExternalForm();
changedC.add(extForm);
}
return Collections.unmodifiableSet(changedC);
}

public Set<String> getChangedClassesWithoutChangedHeaders() throws MojoExecutionException {
Set<String> changedC = new HashSet<>();
for (String c : changedClassesWithoutChangedHeaders) {
URL url = loader.getResource(ChecksumUtil.toClassOrJavaName(c, false));
String extForm = url.toExternalForm();
changedC.add(extForm);
}
return Collections.unmodifiableSet(changedC);
}

public Set<String> getChangedClasses() throws MojoExecutionException {
Set<String> changedC = new HashSet<>();
for (String c : changedClassesWithoutChangedHeaders) {
URL url = loader.getResource(ChecksumUtil.toClassOrJavaName(c, false));
String extForm = url.toExternalForm();
changedC.add(extForm);
}
for (String c : changedClassesWithChangedHeaders) {
URL url = loader.getResource(ChecksumUtil.toClassOrJavaName(c, false));
String extForm = url.toExternalForm();
changedC.add(extForm);
}

return Collections.unmodifiableSet(changedC);
}

public Set<String> getNonAffectedMethods() {
return Collections.unmodifiableSet(nonAffectedMethods);
}

/**
* This method first builds the method-level static dependencies by calling
* MethodLevelStaticDepsBuilder.buildMethodsGraph().
* Then, it computes and retrieves the classes' checksums and the mapping
* between methods and test classes by calling
* MethodLevelStaticDepsBuilder.computeMethodsChecksum(ClassLoader) and
* MethodLevelStaticDepsBuilder.computeMethod2testClasses() respectively.
* Finally, it computes the changed (and impacted) methods through changed
* classes by calling runMethods(boolean).
*/
public void execute() throws MojoExecutionException {
Logger.getGlobal().setLoggingLevel(Level.parse(loggingLevel));
logger = Logger.getGlobal();
Classpath sfClassPath = getSureFireClassPath();
loader = createClassLoader(sfClassPath);

// Build method level static dependencies
try {
MethodLevelStaticDepsBuilder.buildMethodsGraph(includeVariables);
methodToTestClasses = MethodLevelStaticDepsBuilder.computeMethodToTestClasses();
classesChecksum = MethodLevelStaticDepsBuilder.computeClassesChecksums(loader, cleanBytes);
} catch (Exception exception) {
throw new RuntimeException(exception);
}

runHybrid(computeImpactedMethods);
}

/**
* This method handles the main logic of the mojo for hybrid analysis.
* It checks if the file of dependencies exists and sets the changed
* methods accordingly. (First run doesn't have the file of dependencies)
* If the file does not exist, it sets the changed methods, new methods,
* impacted test classes, old classes, changed classes, new classes and
* non-affected methods.
* If the file exists, it sets the changed methods and computes the impacted
* methods and impacted test classes if impacted is true.
* It also updates the methods checksums in the dependency file if
* updateMethodsChecksums is true.
*
* @param impacted a boolean value indicating whether to compute impacted
* methods and impacted test classes
* @throws MojoExecutionException if an exception occurs while setting changed
* methods
*/
protected void runHybrid(boolean impacted) throws MojoExecutionException {
// Checking if the file of dependencies exists (first run)
if (!Files.exists(Paths.get(getArtifactsDir() + METHODS_CHECKSUMS_SERIALIZED_FILE))
&& !Files.exists(Paths.get(getArtifactsDir() + CLASSES_CHECKSUM_SERIALIZED_FILE))) {
// In the first run we compute all method checksums and save them.
// In later runs we just compute new method checksums for changed classes
MethodLevelStaticDepsBuilder.computeMethodsChecksum(loader);
methodsCheckSum = MethodLevelStaticDepsBuilder.getMethodsCheckSum();
changedMethods = new HashSet<>();
newMethods = MethodLevelStaticDepsBuilder.computeMethods();
affectedTestClasses = MethodLevelStaticDepsBuilder.computeTestClasses();
newClasses = MethodLevelStaticDepsBuilder.getClasses();
oldClasses = new HashSet<>();
deletedClasses = new HashSet<>();
changedClassesWithChangedHeaders = new HashSet<>();
changedClassesWithoutChangedHeaders = new HashSet<>();
nonAffectedMethods = new HashSet<>();

if (impacted) {
impactedMethods = newMethods;
impactedClasses = newClasses;
}

if (updateMethodsChecksums) {
try {
// Save class-to-checksums mapping
ZLCHelperMethods.serializeMapping(classesChecksum, getArtifactsDir(),
CLASSES_CHECKSUM_SERIALIZED_FILE);
// The method-to-checksum mapping has been updated in
// ZLCHelperMethods.getChangedDataHybrid()
ZLCHelperMethods.serializeMapping(methodsCheckSum, getArtifactsDir(),
METHODS_CHECKSUMS_SERIALIZED_FILE);
} catch (IOException exception) {
exception.printStackTrace();
}
}
} else {

classDependencyGraph = MethodLevelStaticDepsBuilder.constructClassesDependencyGraph();
MethodLevelStaticDepsBuilder.constuctTestClassesToClassesGraph();
classToTestClassGraph = MethodLevelStaticDepsBuilder.constructClassesToTestClassesGraph();
methodToTestClasses = MethodLevelStaticDepsBuilder.computeMethodToTestClasses();
affectedTestClasses = new HashSet<>();

setChangedAndNonaffectedMethods();

if (impacted) {
computeImpactedMethods();
computeImpactedClasses();
}

if (updateMethodsChecksums) {
try {
// Save class-to-checksums mapping
ZLCHelperMethods.serializeMapping(classesChecksum, getArtifactsDir(),
CLASSES_CHECKSUM_SERIALIZED_FILE);
// Save method-to-checksum mapping
ZLCHelperMethods.serializeMapping(methodsCheckSum, getArtifactsDir(),
METHODS_CHECKSUMS_SERIALIZED_FILE);
} catch (IOException exception) {
exception.printStackTrace();
}
}

}

logInfo(impacted);
}

/**
* This method logs information statements about changed methods, new methods,
* impacted test classes, new classes, old classes and changed classes.
* If impacted is true, it also logs information about impacted methods.
*
* @param impacted a boolean value indicating whether to log information about
* impacted methods
*/
private void logInfo(boolean impacted) {
logger.log(Level.INFO, "ChangedMethods: " + changedMethods.size());
logger.log(Level.INFO, "NewMethods: " + newMethods.size());

if (impacted) {
logger.log(Level.INFO, "ImpactedMethods: " + impactedMethods.size());
}

logger.log(Level.INFO, "NewClasses: " + newClasses.size());
logger.log(Level.INFO, "OldClasses: " + oldClasses.size());
logger.log(Level.INFO, "DeletedClasses: " + deletedClasses.size());
logger.log(Level.INFO, "ChangedClassesWithChangedHeaders: " + changedClassesWithChangedHeaders.size());
logger.log(Level.INFO, "ChangedClassesWithoutChangedHeaders: " + changedClassesWithoutChangedHeaders.size());
logger.log(Level.INFO, "AffectedTestClasses: " + affectedTestClasses.size());

if (impacted) {
logger.log(Level.INFO, "ImpactedClasses: " + impactedClasses.size());
}

// DEBUG PRINTS
if (debug) {
logger.log(Level.INFO, "ImpactedMethods: " + impactedMethods);
logger.log(Level.INFO, "ImpactedClasses: " + impactedClasses);
logger.log(Level.INFO, "AffectedTestClasses: " + affectedTestClasses);
logger.log(Level.INFO, "ClassDependencyGraph: " + classDependencyGraph);
logger.log(Level.INFO, "ClassToTestClassGraph: " + classToTestClassGraph);
logger.log(Level.INFO, "ChangedClassesWithChangedHeaders: " + changedClassesWithChangedHeaders);
logger.log(Level.INFO, "ChangedClassesWithoutChangedHeaders: " + changedClassesWithoutChangedHeaders);
}
}

/**
* Sets the changed and non-affected methods by retrieving changed data using
* the ZLCHelperMethods class and updating the relevant fields.
* This method also updates the impacted test classes by adding test classes
* associated with new methods.
*/
protected void setChangedAndNonaffectedMethods() throws MojoExecutionException {
List<Set<String>> classesData = ZLCHelperMethods.getChangedDataHybridClassLevel(classesChecksum,
getArtifactsDir(), CLASSES_CHECKSUM_SERIALIZED_FILE);

newClasses = classesData == null ? new HashSet<String>() : classesData.get(0);
deletedClasses = classesData == null ? new HashSet<String>() : classesData.get(1);
changedClassesWithChangedHeaders = classesData == null ? new HashSet<String>() : classesData.get(2);
changedClassesWithoutChangedHeaders = classesData == null ? new HashSet<String>() : classesData.get(3);
oldClasses = classesData == null ? new HashSet<String>() : classesData.get(4);

List<Set<String>> methodsData = ZLCHelperMethods.getChangedDataHybridMethodLevel(newClasses, deletedClasses,
changedClassesWithChangedHeaders,
changedClassesWithoutChangedHeaders, MethodLevelStaticDepsBuilder.getMethodsCheckSum(), loader,
getArtifactsDir(), METHODS_CHECKSUMS_SERIALIZED_FILE);

methodsCheckSum = MethodLevelStaticDepsBuilder.getMethodsCheckSum();

changedMethods = methodsData == null ? new HashSet<String>() : methodsData.get(0);
newMethods = methodsData == null ? new HashSet<String>() : methodsData.get(1);

for (String newMethod : newMethods) {
affectedTestClasses.addAll(methodToTestClasses.getOrDefault(newMethod, new HashSet<>()));
}

for (String changedMethod : changedMethods) {
affectedTestClasses.addAll(methodToTestClasses.getOrDefault(changedMethod, new HashSet<>()));
}

for (String addedClass : newClasses) {
affectedTestClasses.addAll(classToTestClassGraph.getOrDefault(addedClass, new HashSet<>()));
}

for (String changedClassesWithChangedHeader : changedClassesWithChangedHeaders) {
affectedTestClasses
.addAll(classToTestClassGraph.getOrDefault(changedClassesWithChangedHeader, new HashSet<>()));
}

}

/**
* Computes the impacted methods by finding impacted methods for changed and new
* methods (called affected methods), and updating the impacted test classes by
* adding test classes found from impacted methods.
*/
private void computeImpactedMethods() {
impactedMethods = new HashSet<>();
impactedMethods.addAll(findImpactedMethods(changedMethods));
impactedMethods.addAll(findImpactedMethods(newMethods));
for (String impactedMethod : impactedMethods) {
affectedTestClasses.addAll(methodToTestClasses.getOrDefault(impactedMethod, new HashSet<String>()));
}
}

/**
* Computes the impacted classes by finding impacted classes for new and
* changedClassesWithChangedHeaders
* methods (called affected methods), and updating the impacted test classes by
* adding test classes found from impacted methods.
*/
private void computeImpactedClasses() {
impactedClasses = new HashSet<>();
impactedClasses.addAll(findImpactedClasses(newClasses));
impactedClasses.addAll(findImpactedClasses(changedClassesWithChangedHeaders));
for (String impactedClass : impactedClasses) {
affectedTestClasses.addAll(classToTestClassGraph.getOrDefault(impactedClass, new HashSet<String>()));
}
}

private Set<String> findImpactedClasses(Set<String> affectedClasses) {
Set<String> classes = new HashSet<>(affectedClasses);
for (String clazz : affectedClasses) {
classes.addAll(MethodLevelStaticDepsBuilder.getClassDeps(clazz));
}
return classes;
}

/**
* This method finds all impacted methods associated with a set of affected
* methods (new methods and changed methods).
* It adds all method dependencies of each affected method to the set of
* impacted methods.
* This is the method that finds the transitive closure of the affected methods.
*
* @param affectedMethods a set of affected methods
* @return a set of impacted methods found from the affected methods
*/
private Set<String> findImpactedMethods(Set<String> affectedMethods) {
Set<String> methods = new HashSet<>(affectedMethods);
for (String method : affectedMethods) {
methods.addAll(MethodLevelStaticDepsBuilder.getMethodDeps(method));

}
return methods;
}

}
Loading

0 comments on commit 46cb80a

Please sign in to comment.