Skip to content
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
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
[![Review Assignment Due Date](https://classroom.github.com/assets/deadline-readme-button-22041afd0340ce965d47ae6ef1cefeee28c7c493a6346c4f15d667ab976d596c.svg)](https://classroom.github.com/a/9A22t-SS)
Разработать standalone приложение, которое имеет следующие возможности:

Принимает на вход проект в виде .jar файла
Expand All @@ -16,4 +17,13 @@

Гайд по использованию ASM: https://asm.ow2.io/asm4-guide.pdf

Дополнительное (необязательное задание): сделайте агента для сбора покрытия по строчкам
Дополнительное (необязательное задание): сделайте агента для сбора покрытия по строчкам

Чтобы получить агента для сбора покрытия по строчкам
```
./gradlew shadowJar
```
Запуск в режиме агента
```
java -javaagent:build/libs/coverage-agent-1.0-all.jar -jar <your jar file>
```
50 changes: 49 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
plugins {
id("java")
id("application")
id("com.github.johnrengelman.shadow") version "8.1.1"
}

group = "org.example"
version = "1.0-SNAPSHOT"
version = "1.0"

repositories {
mavenCentral()
Expand All @@ -22,4 +24,50 @@ dependencies {

tasks.test {
useJUnitPlatform()
}

application {
mainClass.set("org.example.coverage.agent.LineCoverageAgent")
}

tasks.named<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>("shadowJar") {
archiveBaseName.set("coverage-agent")
archiveClassifier.set("all")

from(sourceSets.main.get().output) {
include("org/example/coverage/**")
}

// Включаем все зависимости ASM
configurations = listOf(project.configurations.runtimeClasspath.get())

manifest {
attributes(
"Premain-Class" to "org.example.coverage.LineCoverageAgent",
"Agent-Class" to "org.example.coverage.LineCoverageAgent",
"Can-Redefine-Classes" to "true",
"Can-Retransform-Classes" to "true"
)
}

// Исключаем файлы, которые могут конфликтовать
exclude("META-INF/*.SF")
exclude("META-INF/*.DSA")
exclude("META-INF/*.RSA")
exclude("META-INF/*.txt")
}

tasks.register<Jar>("demoJar") {
archiveBaseName.set("demo-app")
archiveClassifier.set("")

from(sourceSets.main.get().output) {
include("org/example/demo/**")
}

manifest {
attributes("Main-Class" to "org.example.demo.Main")
}

dependsOn("classes")
}
29 changes: 0 additions & 29 deletions src/main/java/org/example/Example.java

This file was deleted.

90 changes: 90 additions & 0 deletions src/main/java/org/example/Main.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package org.example;

import org.example.coverage.CoverageCollector;
import org.example.coverage.CoverageReport;
import org.example.coverage.CoverageTransformer;
import org.example.metrics.Metrics;
import org.example.visitor.ClassAnalyzer;
import org.objectweb.asm.ClassReader;

import java.io.FileWriter;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.util.Enumeration;
import java.util.Locale;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class Main {
private static final CoverageCollector collector = CoverageCollector.getInstance();

public static void premain(String agentArgs, Instrumentation inst) {
inst.addTransformer(new CoverageTransformer(), true);
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("[LineCoverageAgent] Saving coverage report...");
CoverageReport report = collector.generateReport();
try {
report.saveToFile("coverage-report.json");
} catch (IOException e) {
throw new RuntimeException(e);
}
report.printSummary();
}));
}

public static void main(String[] args) throws IOException {
String inputJar = "src/main/resources/fescar.zip";
String outputFile = "src/main/resources/output.json";

Metrics metrics = new Metrics();

try (JarFile inputJarFile = new JarFile(inputJar)) {
Enumeration<JarEntry> enumeration = inputJarFile.entries();

while (enumeration.hasMoreElements()) {
JarEntry entry = enumeration.nextElement();
if (entry.getName().endsWith(".class")) {
ClassReader reader = new ClassReader(inputJarFile.getInputStream(entry));
ClassAnalyzer analyzer = new ClassAnalyzer(metrics);
reader.accept(analyzer, 0);
}
}
}
metrics.calculateClassesDepth();
printMetricsToConsole(metrics);
saveMetricsToJson(metrics, outputFile);
}

private static void printMetricsToConsole(Metrics metrics) {
System.out.println("Max Depth: " + metrics.getMaxDepth());
System.out.println("Average Depth: " + metrics.getAverageDepth());
System.out.println("Average ABC: " + metrics.getAverageAbc());
System.out.println("Average Overridden Methods: " + metrics.getAverageFieldsCount());
System.out.println("Average Fields Count: " + metrics.getAverageOverriddenMethods());

}

private static void saveMetricsToJson(Metrics metrics, String outputFile) {
String json = String.format(Locale.US,
"{\n" +
" \"maxDepth\": %d,\n" +
" \"averageDepth\": %f,\n" +
" \"averageAbc\": %f,\n" +
" \"averageOverriddenMethods\": %f,\n" +
" \"averageFieldsCount\": %f\n" +
"}",
metrics.getMaxDepth(),
metrics.getAverageDepth(),
metrics.getAverageAbc(),
metrics.getAverageOverriddenMethods(),
metrics.getAverageFieldsCount()
);

try (FileWriter writer = new FileWriter(outputFile)) {
writer.write(json);
System.out.println("Metrics saved to file: " + outputFile);
} catch (IOException e) {
System.err.println("Error write file: " + e.getMessage());
}
}
}
47 changes: 47 additions & 0 deletions src/main/java/org/example/coverage/CoverageClassVisitor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package org.example.coverage;

import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

import java.util.HashMap;
import java.util.Map;

class CoverageClassVisitor extends ClassVisitor {

private final String className;
private final Map<Integer, String> lineMapping = new HashMap<>();

public CoverageClassVisitor(ClassVisitor cv, String className) {
super(Opcodes.ASM9, cv);
this.className = className;
}

@Override
public void visitSource(String source, String debug) {
super.visitSource(source, debug);
}

@Override
public MethodVisitor visitMethod(int access, String name,
String descriptor,
String signature, String[] exceptions) {

MethodVisitor mv = super.visitMethod(access, name, descriptor,
signature, exceptions);

// Пропускаем конструкторы и статические блоки инициализации
if (name.equals("<init>") || name.equals("<clinit>")) {
return mv;
}

return new CoverageMethodVisitor(mv, className, name, descriptor, lineMapping);
}

@Override
public void visitEnd() {
// Сохраняем маппинг строк для этого класса
CoverageCollector.getInstance().registerLineMapping(className, lineMapping);
super.visitEnd();
}
}
90 changes: 90 additions & 0 deletions src/main/java/org/example/coverage/CoverageCollector.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package org.example.coverage;

import java.util.*;

public class CoverageCollector {

private static CoverageCollector instance;

private final Map<String, Map<Integer, Integer>> lineCoverage = new HashMap<>();
private final Set<String> executedMethods = new HashSet<>();
private final Map<String, Map<Integer, String>> lineToMethodMapping = new HashMap<>();
private final Map<String, Map<String, Set<Integer>>> methodLines = new HashMap<>();

private CoverageCollector() {}

public static CoverageCollector getInstance() {
if (instance == null) {
instance = new CoverageCollector();
}
return instance;
}

public static void methodEntered(String className, String methodName, String methodDesc) {
getInstance().recordMethodExecution(className, methodName, methodDesc);
}

public static void lineExecuted(String className, String methodName,
String methodDesc, int lineNumber) {
getInstance().recordLineExecution(className, methodName, methodDesc, lineNumber);
}

private void recordMethodExecution(String className, String methodName, String methodDesc) {
String methodKey = createMethodKey(className, methodName, methodDesc);
executedMethods.add(methodKey);
}

private void recordLineExecution(String className, String methodName,
String methodDesc, int lineNumber) {
Map<Integer, Integer> classLines = lineCoverage.computeIfAbsent(
className, k -> new HashMap<>());

int currentCount = classLines.getOrDefault(lineNumber, 0);
classLines.put(lineNumber, currentCount + 1);

// Записываем, какой метод выполнил эту строку
Map<Integer, String> mapping = lineToMethodMapping.computeIfAbsent(
className, k -> new HashMap<>());
mapping.put(lineNumber, methodName);
}

public void registerLineMapping(String className, Map<Integer, String> mapping) {
lineToMethodMapping.put(className, new HashMap<>(mapping));
}

public void registerMethodLines(String className, String methodName,
String methodDesc, Set<Integer> lines) {
Map<String, Set<Integer>> classMethods = methodLines.computeIfAbsent(
className, k -> new HashMap<>());

String methodKey = methodName + methodDesc;
classMethods.put(methodKey, new HashSet<>(lines));
}

public CoverageReport generateReport() {
CoverageReport report = new CoverageReport();

report.setTotalClasses(lineCoverage.size());
report.setTotalMethods(executedMethods.size());
report.setTotalCoveredLines(calculateTotalCoveredLines());
report.setTotalExecutableLines(calculateTotalExecutableLines());

return report;
}

private int calculateTotalCoveredLines() {
return lineCoverage.values().stream()
.mapToInt(Map::size)
.sum();
}

private int calculateTotalExecutableLines() {
return lineToMethodMapping.values().stream()
.mapToInt(Map::size)
.sum();
}

private String createMethodKey(String className, String methodName, String methodDesc) {
return className + "#" + methodName + methodDesc;
}
}
Loading