diff --git a/.gitignore b/.gitignore index 6e7d9a3..465560c 100644 --- a/.gitignore +++ b/.gitignore @@ -108,3 +108,5 @@ gen### VisualStudioCode template !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json +.scannerwork/ +.attach_pid* \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..5179b05 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,19 @@ +language: java + +jdk: oraclejdk8 + +addons: + sonarcloud: + organization: istic-m2-ila-gm + token: + secure: e/yARBIuHIJXfRySzbSLWAeIu652kaZ8ElWZyavAB5XBZlpJX4AneV5jXBGRtlAnka77KaR1uL9f/pE3Itnchvdz1P12VFWTxFWkNFe2c4Ms0zkieAdAF//4k8p81G6mfKP8y4U0Jf2dqjj3+SNc4Dn2aoYZvL2mcpT0hPQiY4wjlE6qI12A72IfnUDgR6zr28qw7IkPho6LLwac8Qk3lL8qNJ2n6gP2OX05YsmSSv+XRAxZ5EpTwEg4HCsiB8ziqixFhaih4UcIfJ2q6OS/y1j3cdpwL11EgSrvvz104a8jj77hcKx/IAPRUFGYnb8N3DGm9fNgMviZdR8hgWBFWbRc7CHNo9L0PJVHUe/T8TTSeisKz/Id67KLw8nBS9eQUE3/ljuEN9HcvQLB2uwNWYIodLYgjougax9VyVft/0zlcLaKjYUhFpGkpmsEToST8/++CbB8UeYGZX8G7Dn6gtib0iD+Cl1Lcz0ZcAn+nvWXbL8Ix+WXSRqkhJk9gmR5RCUpPI3cFspS9t82ey0DYrzEbH9mh+1o8sVvkRp+xExU5PuAdKdCY7pz+a4/f2ulsktpfbH5cxUly+NQHDtVt654xj/rfW6UhEmD5DFJR7OrAlnuYAIzsjQkeI4e3X131kOWJ+BiLb0ZuHZ4b0v7RiboVEwo5/zcyymUZZcsMq8= + +cache: + directories: + - "$HOME/.m2/repository" + - "$HOME/.sonar/cache" + +jobs: + include: + - script: cd fake && mvn test && cd .. && mvn -B org.jacoco:jacoco-maven-plugin:prepare-agent verify && sonar-scanner + name: Unit & Quality \ No newline at end of file diff --git a/README.md b/README.md index c0ae0bb..f5226c4 100644 --- a/README.md +++ b/README.md @@ -1 +1,47 @@ -# WeAssert \ No newline at end of file +# WeAssert + +## Project Status + +[![Build Status](https://travis-ci.org/ISTIC-M2-ILa-GM/WeAssert.svg?branch=dev)](https://travis-ci.org/ISTIC-M2-ILa-GM/WeAssert) +[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=fr.istic.gm.weassert&metric=code_smells)](https://sonarcloud.io/dashboard?id=fr.istic.gm.weassert) +[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=fr.istic.gm.weassert&metric=coverage)](https://sonarcloud.io/dashboard?id=fr.istic.gm.weassert) + +## Links + +* Subject: https://github.com/Software-Testing/Projects-2018-2019 +* Project: https://github.com/ISTIC-M2-ILa-GM/WeAssert/projects/1 +* Issues and Features TODO: https://github.com/ISTIC-M2-ILa-GM/WeAssert/issues +* Continuous integration - Travis CI: https://travis-ci.org/ISTIC-M2-ILa-GM/WeAssert +* Quality - Sonarcloud: https://sonarcloud.io/dashboard?id=fr.istic.gm.weassert + +## Build +``` +mvn compile +``` +This project needs to be installed to can be used: +``` +mvn install +``` +For a quicker install you can skip tests: +``` +mvn install -DskipTests +``` + +## Execute the jar +``` +java -jar weassert.jar +``` + +## How to use the project + +* Start the application, +* On the GUI: + * Select maven executable, + * Select the project, + * Click on generate button, + * Click on run tests, + * Select test and see modified code. + +## Tested projects + +* we-assert-fake-test \ No newline at end of file diff --git a/fake/pom.xml b/fake/pom.xml new file mode 100644 index 0000000..490a508 --- /dev/null +++ b/fake/pom.xml @@ -0,0 +1,135 @@ + + + + fr.istic.gm + we-assert-fake-test + 1.0 + 4.0.0 + FakeProject + + 2018 + + + GM + https://github.com/ISTIC-M2-ILa-GM + + + + + mlh + Gwenole LE HENAFF + + + grl + Gautier Rouleau + + + + + 1.8 + 1.8 + 3.1 + 3.1.0 + 3.1.0 + 2.4 + 2.19 + 3.0.0 + 0.8.2 + + 1.7.25 + 1.18.2 + + 4.12 + 1.3 + 2.22.0 + 7.2.0.RELEASE + + + + + + org.projectlombok + lombok + ${lombok.version} + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + + junit + junit + ${junit4.version} + + + + + org.hamcrest + hamcrest-all + ${hamcrest.version} + test + + + org.mockito + mockito-core + ${mockito.core.version} + test + + + uk.co.jemos.podam + podam + ${podam.version} + test + + + + + + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + maven-compiler-plugin + ${maven-compiler.version} + + ${java.version.source} + ${java.version.target} + true + + + + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + + + **/config/** + **/*Exception.java + **/*Configuration.java + + + + + + prepare-agent + + + + report + prepare-package + + report + + + + + + + \ No newline at end of file diff --git a/fake/src/main/java/fr/istic/gm/weassert/fake/Person.java b/fake/src/main/java/fr/istic/gm/weassert/fake/Person.java new file mode 100644 index 0000000..61ff6da --- /dev/null +++ b/fake/src/main/java/fr/istic/gm/weassert/fake/Person.java @@ -0,0 +1,17 @@ +package fr.istic.gm.weassert.fake; + +import lombok.Data; + +/** + * A fake class Person to test the projet + */ +@Data +public class Person { + + private String name; + private int age; + + public boolean isAdult() { + return age >= 18; + } +} \ No newline at end of file diff --git a/fake/src/test/java/fr/istic/gm/weassert/fake/PersonTest.java b/fake/src/test/java/fr/istic/gm/weassert/fake/PersonTest.java new file mode 100644 index 0000000..d8ab65c --- /dev/null +++ b/fake/src/test/java/fr/istic/gm/weassert/fake/PersonTest.java @@ -0,0 +1,21 @@ +package fr.istic.gm.weassert.fake; + +import org.junit.Test; + +import static org.junit.Assert.assertFalse; + +/** + * A fake Test class to try to generate assert + */ +public class PersonTest { + + @Test + public void testAge() { + + Person p = new Person(); + p.setAge(13); + p.setName("name"); + + assertFalse(p.isAdult()); + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 0a81743..6c37b72 100644 --- a/pom.xml +++ b/pom.xml @@ -5,10 +5,10 @@ fr.istic.gm we-assert - 1.0-SNAPSHOT + 1.0 WeAssert - 2018 + 2019 GM @@ -45,93 +45,118 @@ 1.7.25 1.18.2 + 3.24.0-GA + 5.2 + 4.12 1.3 2.22.0 7.2.0.RELEASE - - - - org.projectlombok - lombok - ${lombok.version} - + + + + org.projectlombok + lombok + ${lombok.version} + + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + org.slf4j + slf4j-simple + ${slf4j.version} + + + + + org.javassist + javassist + ${javassist.version} + - - - org.slf4j - slf4j-api - ${slf4j.version} - + + + org.ow2.asm + asm-debug-all + ${asm.version} + - - - junit - junit - ${junit4.version} - test - + + + junit + junit + ${junit4.version} + - - - org.hamcrest - hamcrest-all - ${hamcrest.version} - test - - - org.mockito - mockito-core - ${mockito.core.version} - test - - - uk.co.jemos.podam - podam - ${podam.version} - test - - + + + org.hamcrest + hamcrest-all + ${hamcrest.version} + test + + + org.mockito + mockito-core + ${mockito.core.version} + test + + + uk.co.jemos.podam + podam + ${podam.version} + test + + - - - - maven-surefire-plugin - ${maven-surefire-plugin.version} - - - maven-compiler-plugin - ${maven-compiler.version} - - - org.jacoco - jacoco-maven-plugin - ${jacoco.version} - - - **/config/** - **/*Exception.java - **/*Configuration.java - - - - - - prepare-agent - - - - report - prepare-package - - report - - - - - - + + + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + maven-compiler-plugin + ${maven-compiler.version} + + ${java.version.source} + ${java.version.target} + true + + + + org.jacoco + jacoco-maven-plugin + ${jacoco.version} + + + src/main/java/fr/istic/gm/weassert/test/model/* + **/config/** + **/*Exception.java + **/*Configuration.java + + + + + + prepare-agent + + + + report + prepare-package + + report + + + + + \ No newline at end of file diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..3562da2 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,14 @@ +sonar.host.url=https://sonarcloud.io +sonar.projectKey=fr.istic.gm.weassert +sonar.projectName=WeAssert +sonar.organization=istic-m2-ila-gm +sonar.sources=src/main/java +sonar.projectVersion=1.0.0 +sonar.language=java +sonar.coverage.exclusions=**/config/**,**/*Exception.java, **/*Configuration.java,src/main/java/fr/istic/gm/weassert/test/model/* +sonar.java.source=1.8 +sonar.java.binaries=. +sonar.java.libraries=/home/travis/.m2 +sonar.links.homepage=https://github.com/ISTIC-M2-ILa-GM/WeAssert +sonar.links.ci=https://travis-ci.org/ISTIC-M2-ILa-GM/WeAssert +sonar.links.scm=git@github.com:ISTIC-M2-ILa-GM/WeAssert.git \ No newline at end of file diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 0000000..0100d39 --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: fr.istic.gm.weassert.TestRunnerApp + diff --git a/src/main/java/fr/istic/gm/weassert/App.java b/src/main/java/fr/istic/gm/weassert/App.java deleted file mode 100644 index 9a05f9b..0000000 --- a/src/main/java/fr/istic/gm/weassert/App.java +++ /dev/null @@ -1,8 +0,0 @@ -package fr.istic.gm.weassert; - -public class App { - - public static void main(String... args) { - - } -} diff --git a/src/main/java/fr/istic/gm/weassert/TestRunnerApp.java b/src/main/java/fr/istic/gm/weassert/TestRunnerApp.java new file mode 100644 index 0000000..08ed748 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/TestRunnerApp.java @@ -0,0 +1,23 @@ +package fr.istic.gm.weassert; + +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; + +public class TestRunnerApp extends Application { + public static void main(String... args) { + launch(args); + } + + @Override + public void start(Stage primaryStage) throws Exception { + FXMLLoader loader = new FXMLLoader(getClass().getResource("/rootPane.fxml")); + Parent root = loader.load(); + + primaryStage.setTitle("WeAssert"); + primaryStage.setScene(new Scene(root, 800, 600)); + primaryStage.show(); + } +} diff --git a/src/main/java/fr/istic/gm/weassert/controller/MainController.java b/src/main/java/fr/istic/gm/weassert/controller/MainController.java new file mode 100644 index 0000000..5ed1968 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/controller/MainController.java @@ -0,0 +1,163 @@ +package fr.istic.gm.weassert.controller; + +import com.sun.javafx.collections.ObservableListWrapper; +import fr.istic.gm.weassert.test.application.WeAssertRunner; +import fr.istic.gm.weassert.test.application.impl.WeAssertRunnerImpl; +import fr.istic.gm.weassert.test.runner.TestRunnerListener; +import fr.istic.gm.weassert.test.utils.FileUtils; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.chart.PieChart; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.MultipleSelectionModel; +import javafx.scene.control.TreeItem; +import javafx.scene.control.TreeView; +import javafx.scene.web.WebEngine; +import javafx.scene.web.WebView; +import javafx.stage.DirectoryChooser; +import javafx.stage.FileChooser; +import lombok.AccessLevel; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import org.junit.runner.Result; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import static javafx.scene.chart.PieChart.Data; + +@Slf4j +public class MainController { + @FXML + public PieChart testResultsChart; + + @FXML + public Label selectedMaven; + + @FXML + public Label selectedFile; + + @FXML + public Button browseButton; + + @FXML + public WebView webView; + + @FXML + public TreeView treeView; + + private File projectDirectory; + + private File mavenBinary; + + private int passedTest = 0; + + private int failedTests = 0; + + private WebEngine webEngine; + + @Setter(AccessLevel.PACKAGE) + private WeAssertRunner weAssertRunner; + + public void initialize() { + this.webEngine = this.webView.getEngine(); + TreeItem rootItem = new TreeItem<>("[No project selected]"); + rootItem.setExpanded(true); + treeView.setRoot(rootItem); + + this.testResultsChart.setStartAngle(45); + + ObservableList observableList = new ObservableListWrapper<>(Arrays.asList(new Data("Passed tests", this.passedTest), new Data("Failed tests", this.failedTests))); + this.testResultsChart.setData(observableList); + } + + public void browseAction() { + DirectoryChooser directoryChooser = new DirectoryChooser(); + this.projectDirectory = directoryChooser.showDialog(null); + if (this.projectDirectory == null) { + this.selectedFile.setText("[No project selected]"); + } else { + if (this.projectDirectory.isDirectory()) { + this.selectedFile.setText(this.projectDirectory.getPath()); + + this.weAssertRunner = new WeAssertRunnerImpl( + projectDirectory.getAbsolutePath(), + this.mavenBinary != null ? this.mavenBinary.getAbsolutePath() : "/usr/bin/mvn", + new TestRunnerListener() { + @Override + public void testRunFinished(Result result) throws Exception { + passedTest += result.getRunCount() - result.getFailureCount(); + failedTests += result.getFailureCount(); + Data passedTestData = new Data("Passed tests", passedTest); + + Data failedTestData = new Data("Failed tests", failedTests); + + ObservableList observableList = new ObservableListWrapper<>(Arrays.asList(passedTestData, failedTestData)); + testResultsChart.setData(observableList); + } + } + ); + + showTestFiles(this.projectDirectory); + } + } + } + + public void selectMavenAction() { + FileChooser fileChooser = new FileChooser(); + this.mavenBinary = fileChooser.showOpenDialog(null); + if (this.mavenBinary == null) { + this.selectedMaven.setText("[System default]"); + } else { + this.selectedMaven.setText(this.projectDirectory.getPath()); + } + } + + public void generateAction() { + this.weAssertRunner.generate(); + } + + public void testAction() { + passedTest = 0; + failedTests = 0; + this.weAssertRunner.runTests(); + } + + public void itemClickedAction() { + MultipleSelectionModel selectionModel = this.treeView.getSelectionModel(); + ObservableList selectedItems = selectionModel.getSelectedItems(); + this.displaySourceCode(selectedItems.get(0).getValue().toString()); + } + + private void displaySourceCode(String s) { + File sourceFile = new File(s); + if (sourceFile.isFile() && !sourceFile.isDirectory()) { + try { + String sourceCode = new String(Files.readAllBytes(sourceFile.toPath())); + + this.webEngine.loadContent(String.format("
\n" +
+                        "%s\n" +
+                        "
", sourceCode)); + } catch (IOException e) { + log.error("Can't read a source file", e); + } + } + } + + private void showTestFiles(File directory) { + this.treeView.getRoot().setValue(directory.getName()); + this.treeView.getRoot().getChildren().clear(); + + List> collect = FileUtils.findFilesFromFolder(directory) + .stream().filter(f -> f.getName().endsWith("Test.java")) + .map(f -> new TreeItem<>(f.getAbsolutePath())) + .collect(Collectors.toList()); + + this.treeView.getRoot().getChildren().addAll(collect); + } +} diff --git a/src/main/java/fr/istic/gm/weassert/test/analyser/CodeVisitor.java b/src/main/java/fr/istic/gm/weassert/test/analyser/CodeVisitor.java new file mode 100644 index 0000000..dd286a2 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/analyser/CodeVisitor.java @@ -0,0 +1,14 @@ +package fr.istic.gm.weassert.test.analyser; + +import fr.istic.gm.weassert.test.model.VariableDefinition; + +import java.util.Map; + +public interface CodeVisitor { + + Map getVariableValues(); + + void initVariableValues(); + + void visit(Class clazz, String methodName, String methodDefinition, String variableName, Object variableValue); +} diff --git a/src/main/java/fr/istic/gm/weassert/test/analyser/LocalVariableParser.java b/src/main/java/fr/istic/gm/weassert/test/analyser/LocalVariableParser.java new file mode 100644 index 0000000..cad7053 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/analyser/LocalVariableParser.java @@ -0,0 +1,13 @@ +package fr.istic.gm.weassert.test.analyser; + +import fr.istic.gm.weassert.test.model.LocalVariableParsed; + +import java.util.List; + +public interface LocalVariableParser { + + Class getClazz(); + + List parse(); + +} diff --git a/src/main/java/fr/istic/gm/weassert/test/analyser/TestAnalyser.java b/src/main/java/fr/istic/gm/weassert/test/analyser/TestAnalyser.java new file mode 100644 index 0000000..2b6cdfe --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/analyser/TestAnalyser.java @@ -0,0 +1,9 @@ +package fr.istic.gm.weassert.test.analyser; + +import fr.istic.gm.weassert.test.model.TestAnalysed; + +import java.util.List; + +public interface TestAnalyser { + List analyse(); +} diff --git a/src/main/java/fr/istic/gm/weassert/test/analyser/impl/CodeVisitorImpl.java b/src/main/java/fr/istic/gm/weassert/test/analyser/impl/CodeVisitorImpl.java new file mode 100644 index 0000000..0cac664 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/analyser/impl/CodeVisitorImpl.java @@ -0,0 +1,41 @@ +package fr.istic.gm.weassert.test.analyser.impl; + +import fr.istic.gm.weassert.test.analyser.CodeVisitor; +import fr.istic.gm.weassert.test.model.VariableDefinition; +import lombok.extern.slf4j.Slf4j; + +import java.util.HashMap; +import java.util.Map; + +@Slf4j +public class CodeVisitorImpl implements CodeVisitor { + + public static final CodeVisitor INSTANCE = new CodeVisitorImpl(); + + private Map variableValues; + + @Override + public Map getVariableValues() { + if (variableValues == null) { + variableValues = new HashMap<>(); + } + return variableValues; + } + + @Override + public void initVariableValues() { + variableValues = new HashMap<>(); + } + + @Override + public void visit(Class clazz, String methodName, String methodDesc, String variableName, Object variableValue) { + + VariableDefinition variableDefinition = VariableDefinition.builder() + .clazz(clazz) + .methodName(methodName) + .methodDesc(methodDesc) + .variableName(variableName) + .build(); + getVariableValues().put(variableDefinition, variableValue); + } +} diff --git a/src/main/java/fr/istic/gm/weassert/test/analyser/impl/LocalVariableParserImpl.java b/src/main/java/fr/istic/gm/weassert/test/analyser/impl/LocalVariableParserImpl.java new file mode 100644 index 0000000..ccd4e16 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/analyser/impl/LocalVariableParserImpl.java @@ -0,0 +1,112 @@ +package fr.istic.gm.weassert.test.analyser.impl; + +import fr.istic.gm.weassert.test.analyser.LocalVariableParser; +import fr.istic.gm.weassert.test.model.LocalVariableParsed; +import fr.istic.gm.weassert.test.utils.ClassReaderFactory; +import fr.istic.gm.weassert.test.utils.UrlClassLoaderWrapper; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.LocalVariableNode; +import org.objectweb.asm.tree.MethodNode; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import static java.util.Arrays.asList; + +@Slf4j +public class LocalVariableParserImpl implements LocalVariableParser { + + private static final List PRIMITIVE_TYPE = asList(Type.BOOLEAN, Type.DOUBLE, Type.FLOAT, Type.INT, Type.LONG, Type.CHAR, Type.SHORT); + + private ClassReader classReader; + + private ClassNode classNode; + + private UrlClassLoaderWrapper urlClassLoaderWrapper; + + @Getter + private Class clazz; + + public LocalVariableParserImpl(ClassReaderFactory classReaderFactory, Class clazz, ClassNode classNode, UrlClassLoaderWrapper urlClassLoaderWrapper) { + this.classReader = classReaderFactory.create(clazz); + this.clazz = clazz; + this.classNode = classNode; + this.urlClassLoaderWrapper = urlClassLoaderWrapper; + } + + public List parse() { + + log.info("LOCAL VARIABLE PARSE..."); + classReader.accept(classNode, 0); + List variableParseds = mapMethodsToLocalVariableParsed(classNode.methods); + log.info("LOCAL VARIABLE PARSED: " + variableParseds); + + return variableParseds; + } + + private List mapMethodsToLocalVariableParsed(List methods) { + return methods.stream() + .filter(m -> !"".equals(m.name)) + .map(m -> LocalVariableParsed.builder() + .name(m.name) + .desc(m.desc) + .localVariables(retrieveLocalVariables(m)) + .build()) + .collect(Collectors.toList()); + } + + private List retrieveLocalVariables(MethodNode methodNode) { + return methodNode.localVariables.stream() + .filter(v -> !"this".equals(v.name)) + .map(this::retrieveVariableName) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + } + + private List retrieveVariableName(LocalVariableNode localVariable) { + List variables = new ArrayList<>(); + Type t = Type.getType(localVariable.desc); + if (t.getSort() == Type.OBJECT) { + variables.addAll(retrieveGetters(localVariable, t)); + } else if (PRIMITIVE_TYPE.contains(t.getSort())) { + variables.add(localVariable.name); + } + return variables; + } + + private List retrieveGetters(LocalVariableNode localVariable, Type type) { + List variables = new ArrayList<>(); + Class c = urlClassLoaderWrapper.getClassList().stream().filter(cl -> cl.getName().equals(type.getClassName())).findFirst().orElse(null); + if (c != null) { + for (Field f : c.getDeclaredFields()) { + if (f.getName() == null || f.getName().isEmpty()) { + continue; + } + String methodName = String.format("get%s%s", f.getName().substring(0, 1).toUpperCase(), f.getName().substring(1)); + if (contains(c.getDeclaredMethods(), methodName)) { + variables.add(String.format("%s.%s()", localVariable.name, methodName)); + } + } + } + return variables; + } + + private boolean contains(Method[] declaredMethods, String methodName) { + for (Method m : declaredMethods) { + if (methodName.equals(m.getName())) { + return true; + } + } + return false; + } +} diff --git a/src/main/java/fr/istic/gm/weassert/test/analyser/impl/TestAnalyserImpl.java b/src/main/java/fr/istic/gm/weassert/test/analyser/impl/TestAnalyserImpl.java new file mode 100644 index 0000000..101b828 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/analyser/impl/TestAnalyserImpl.java @@ -0,0 +1,99 @@ +package fr.istic.gm.weassert.test.analyser.impl; + +import fr.istic.gm.weassert.test.generator.CodeWriter; +import fr.istic.gm.weassert.test.analyser.CodeVisitor; +import fr.istic.gm.weassert.test.analyser.LocalVariableParser; +import fr.istic.gm.weassert.test.analyser.TestAnalyser; +import fr.istic.gm.weassert.test.compiler.SourceCodeCompiler; +import fr.istic.gm.weassert.test.exception.WeAssertException; +import fr.istic.gm.weassert.test.model.LocalVariableParsed; +import fr.istic.gm.weassert.test.model.MethodDefinition; +import fr.istic.gm.weassert.test.model.TestAnalysed; +import fr.istic.gm.weassert.test.model.VariableDefinition; +import fr.istic.gm.weassert.test.runner.TestRunner; +import fr.istic.gm.weassert.test.utils.UrlClassLoaderWrapper; +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static fr.istic.gm.weassert.test.utils.ClassResolverUtil.mapClassToClassPath; + +@Slf4j +@AllArgsConstructor +public class TestAnalyserImpl implements TestAnalyser { + + private static final String VISITOR_CODE = "%s.INSTANCE.visit(getClass(), \"%s\", \"%s\", \"%s\", %s); // GENERATED VISITOR"; + + private LocalVariableParser localVariableParser; + + private CodeWriter codeWriter; + + private CodeVisitor codeVisitor; + + private SourceCodeCompiler sourceCodeCompiler; + + private UrlClassLoaderWrapper urlClassLoaderWrapper; + + private TestRunner testRunner; + + @Override + public List analyse() { + + log.info("ANALYSE..."); + addVisitorToTests(); + Class refreshedClass = retrieveClass(); + Map firstVariableValues = runTestsAndRetrieveFistVariableValues(refreshedClass); + List result = createAnalyseResult(firstVariableValues, codeVisitor.getVariableValues()); + + log.info(String.format("ANALYSED: %s", result)); + return result; + } + + private Class retrieveClass() { + return urlClassLoaderWrapper.getClassList().stream() + .filter(c -> mapClassToClassPath(c).equals(mapClassToClassPath(localVariableParser.getClazz()))) + .findFirst() + .orElseThrow(() -> new WeAssertException("Can't reload refreshed class")); + } + + private void addVisitorToTests() { + List parse = localVariableParser.parse(); + parse.forEach(p -> + p.getLocalVariables().forEach(v -> + { + String visitor = String.format(VISITOR_CODE, codeVisitor.getClass().getName(), p.getName(), p.getDesc(), v, v); + codeWriter.insertOne(p.getName(), p.getDesc(), visitor); + } + )); + codeWriter.writeAndCloseFile(); + sourceCodeCompiler.compileAndWait(); + } + + private Map runTestsAndRetrieveFistVariableValues(Class clazz) { + testRunner.startTest(clazz); + Map firstVariableValues = codeVisitor.getVariableValues(); + codeVisitor.initVariableValues(); + testRunner.startTest(clazz); + return firstVariableValues; + } + + private List createAnalyseResult(Map firstVariableValues, Map secondsVariableValues) { + Map testAnalyseds = new HashMap<>(); + firstVariableValues.keySet().stream().filter(k -> secondsVariableValues.containsKey(k) && secondsVariableValues.get(k).equals(firstVariableValues.get(k))).forEach(k -> { + MethodDefinition methodDefinition = MethodDefinition.builder().clazz(k.getClazz()).methodName(k.getMethodName()).methodDesc(k.getMethodDesc()).build(); + TestAnalysed testAnalysed; + if (testAnalyseds.containsKey(methodDefinition)) { + testAnalysed = testAnalyseds.get(methodDefinition); + } else { + testAnalysed = TestAnalysed.builder().clazz(k.getClazz()).methodName(k.getMethodName()).methodDesc(k.getMethodDesc()).build(); + testAnalyseds.put(methodDefinition, testAnalysed); + } + testAnalysed.getVariableValues().put(k.getVariableName(), firstVariableValues.get(k)); + }); + return new ArrayList<>(testAnalyseds.values()); + } +} diff --git a/src/main/java/fr/istic/gm/weassert/test/application/WeAssertRunner.java b/src/main/java/fr/istic/gm/weassert/test/application/WeAssertRunner.java new file mode 100644 index 0000000..2e98aa5 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/application/WeAssertRunner.java @@ -0,0 +1,6 @@ +package fr.istic.gm.weassert.test.application; + +public interface WeAssertRunner { + void generate(); + void runTests(); +} diff --git a/src/main/java/fr/istic/gm/weassert/test/application/impl/WeAssertRunnerImpl.java b/src/main/java/fr/istic/gm/weassert/test/application/impl/WeAssertRunnerImpl.java new file mode 100644 index 0000000..775225a --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/application/impl/WeAssertRunnerImpl.java @@ -0,0 +1,80 @@ +package fr.istic.gm.weassert.test.application.impl; + +import fr.istic.gm.weassert.test.generator.AssertionGenerator; +import fr.istic.gm.weassert.test.analyser.LocalVariableParser; +import fr.istic.gm.weassert.test.analyser.TestAnalyser; +import fr.istic.gm.weassert.test.analyser.impl.CodeVisitorImpl; +import fr.istic.gm.weassert.test.analyser.impl.LocalVariableParserImpl; +import fr.istic.gm.weassert.test.analyser.impl.TestAnalyserImpl; +import fr.istic.gm.weassert.test.application.WeAssertRunner; +import fr.istic.gm.weassert.test.compiler.SourceCodeCompiler; +import fr.istic.gm.weassert.test.compiler.impl.SourceCodeCompilerImpl; +import fr.istic.gm.weassert.test.generator.impl.AssertionGeneratorImpl; +import fr.istic.gm.weassert.test.generator.impl.SourceCodeWriter; +import fr.istic.gm.weassert.test.model.TestAnalysed; +import fr.istic.gm.weassert.test.runner.TestRunner; +import fr.istic.gm.weassert.test.runner.TestRunnerListener; +import fr.istic.gm.weassert.test.runner.impl.TestRunnerImpl; +import fr.istic.gm.weassert.test.utils.BackupUtils; +import fr.istic.gm.weassert.test.utils.UrlClassLoaderWrapper; +import fr.istic.gm.weassert.test.utils.impl.BackupUtilsImpl; +import fr.istic.gm.weassert.test.utils.impl.ClassReaderFactoryImpl; +import fr.istic.gm.weassert.test.utils.impl.ProcessBuilderFactoryImpl; +import fr.istic.gm.weassert.test.utils.impl.UrlClassLoaderWrapperImpl; +import org.junit.runner.JUnitCore; +import org.objectweb.asm.tree.ClassNode; + +import java.util.List; +import java.util.stream.Collectors; + +import static fr.istic.gm.weassert.test.utils.ClassResolverUtil.mapClassToSourcePath; +import static java.util.Arrays.asList; + +public class WeAssertRunnerImpl implements WeAssertRunner { + + private static final String TEST_END_FILENAME = "Test"; + private static final List DEFAULT_MAVEN_FILE_PATH = asList("%s/target/classes/", "%s/target/test-classes/"); + + private String mavenProjectPath; + + private String mavenCommand; + + private TestRunnerListener testRunnerListener; + + public WeAssertRunnerImpl(String mavenProjectPath, String mavenCommand, TestRunnerListener testRunnerListener) { + this.mavenProjectPath = mavenProjectPath; + this.mavenCommand = mavenCommand; + this.testRunnerListener = testRunnerListener; + } + + @Override + public void generate() { + List filePaths = getMavenFilePath(); + UrlClassLoaderWrapper urlClassLoaderWrapper = new UrlClassLoaderWrapperImpl(filePaths); + urlClassLoaderWrapper.getClassList().stream().filter(c -> c.getName().endsWith(TEST_END_FILENAME)).forEach(c -> { + LocalVariableParser localVariableParser = new LocalVariableParserImpl(new ClassReaderFactoryImpl(), c, new ClassNode(), urlClassLoaderWrapper); + SourceCodeCompiler codeCompiler = new SourceCodeCompilerImpl(mavenProjectPath, mavenCommand, new ProcessBuilderFactoryImpl(), urlClassLoaderWrapper); + String sourcePath = mapClassToSourcePath(c); + BackupUtils backupUtils = new BackupUtilsImpl(sourcePath); + TestAnalyser testAnalyser = new TestAnalyserImpl(localVariableParser, new SourceCodeWriter(sourcePath), CodeVisitorImpl.INSTANCE, codeCompiler, urlClassLoaderWrapper, new TestRunnerImpl(new JUnitCore(), testRunnerListener)); + List testAnalyseds = testAnalyser.analyse(); + backupUtils.restore(); + AssertionGenerator assertionGenerator = new AssertionGeneratorImpl(new SourceCodeWriter(sourcePath)); + assertionGenerator.generate(testAnalyseds); + }); + } + + @Override + public void runTests() { + List filePaths = getMavenFilePath(); + UrlClassLoaderWrapper urlClassLoaderWrapper = new UrlClassLoaderWrapperImpl(filePaths); + urlClassLoaderWrapper.getClassList().stream().filter(c -> c.getName().endsWith(TEST_END_FILENAME)).forEach(c -> { + TestRunner testRunner = new TestRunnerImpl(new JUnitCore(), testRunnerListener); + testRunner.startTest(c); + }); + } + + private List getMavenFilePath() { + return DEFAULT_MAVEN_FILE_PATH.stream().map(s -> String.format(s, mavenProjectPath)).collect(Collectors.toList()); + } +} diff --git a/src/main/java/fr/istic/gm/weassert/test/compiler/SourceCodeCompiler.java b/src/main/java/fr/istic/gm/weassert/test/compiler/SourceCodeCompiler.java new file mode 100644 index 0000000..2305296 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/compiler/SourceCodeCompiler.java @@ -0,0 +1,5 @@ +package fr.istic.gm.weassert.test.compiler; + +public interface SourceCodeCompiler { + void compileAndWait(); +} diff --git a/src/main/java/fr/istic/gm/weassert/test/compiler/impl/SourceCodeCompilerImpl.java b/src/main/java/fr/istic/gm/weassert/test/compiler/impl/SourceCodeCompilerImpl.java new file mode 100644 index 0000000..c22562b --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/compiler/impl/SourceCodeCompilerImpl.java @@ -0,0 +1,103 @@ +package fr.istic.gm.weassert.test.compiler.impl; + +import fr.istic.gm.weassert.test.compiler.SourceCodeCompiler; +import fr.istic.gm.weassert.test.exception.WeAssertException; +import fr.istic.gm.weassert.test.utils.BackupUtils; +import fr.istic.gm.weassert.test.utils.ProcessBuilderFactory; +import fr.istic.gm.weassert.test.utils.ProcessBuilderWrapper; +import fr.istic.gm.weassert.test.utils.UrlClassLoaderWrapper; +import fr.istic.gm.weassert.test.utils.impl.BackupUtilsImpl; +import lombok.extern.slf4j.Slf4j; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NodeList; +import org.w3c.dom.Text; + +import javax.xml.XMLConstants; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import java.io.BufferedReader; +import java.io.File; +import java.io.InputStreamReader; +import java.nio.file.Paths; + +@Slf4j +public class SourceCodeCompilerImpl implements SourceCodeCompiler { + + private static final String POM_FILE = "/pom.xml"; + private static final String ERROR_NO_POM = "SourceCodeCompilerImpl: No pom file on the maven project %s"; + + private String mavenProjectPath; + + private ProcessBuilderWrapper processBuilder; + + private UrlClassLoaderWrapper urlClassLoaderWrapper; + + public SourceCodeCompilerImpl(String mavenProjectPath, String mavenCommand, ProcessBuilderFactory processBuilderFactory, UrlClassLoaderWrapper urlClassLoaderWrapper) { + this.mavenProjectPath = mavenProjectPath + POM_FILE; + this.urlClassLoaderWrapper = urlClassLoaderWrapper; + if (!Paths.get(mavenProjectPath).toFile().exists()) { + throw new WeAssertException(String.format(ERROR_NO_POM, this.mavenProjectPath)); + } + processBuilder = processBuilderFactory.create(mavenProjectPath, mavenCommand, "clean", "test", "-DskipTests"); + } + + @Override + public void compileAndWait() { + try { + BackupUtils backupUtils = insertDeps(); + Process start = processBuilder.start(); + BufferedReader buff = new BufferedReader(new InputStreamReader(start.getInputStream())); + while (start.isAlive()) { + String line; + while ((line = buff.readLine()) != null) { + log.info(line); + } + } + urlClassLoaderWrapper.refresh(); + backupUtils.restore(); + } catch (Exception e) { + throw new WeAssertException("SourceCodeCompilerImpl: Compile error", e); + } + } + + private BackupUtils insertDeps() { + try { + BackupUtils backupUtils = new BackupUtilsImpl(mavenProjectPath); + DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance(); + domFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + DocumentBuilder builder = domFactory.newDocumentBuilder(); + Document doc = builder.parse(new File(mavenProjectPath)); + NodeList nodes = doc.getElementsByTagName("dependencies"); + Element dependency = doc.createElement("dependency"); + Element groupId = doc.createElement("groupId"); + Element artifactId = doc.createElement("artifactId"); + Element version = doc.createElement("version"); + Text groupIdText = doc.createTextNode("fr.istic.gm"); + Text artifactIdText = doc.createTextNode("we-assert"); + Text versionText = doc.createTextNode("1.0"); + groupId.appendChild(groupIdText); + artifactId.appendChild(artifactIdText); + version.appendChild(versionText); + dependency.appendChild(groupId); + dependency.appendChild(artifactId); + dependency.appendChild(version); + nodes.item(0).insertBefore(dependency, nodes.item(0).getChildNodes().item(0).getNextSibling()); + TransformerFactory factory = TransformerFactory.newInstance(); + factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true); + Transformer transformer = factory.newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + DOMSource domSource = new DOMSource(doc); + StreamResult streamResult = new StreamResult(new File(mavenProjectPath)); + transformer.transform(domSource, streamResult); + return backupUtils; + } catch (Exception e) { + throw new WeAssertException("Can't insert deps", e); + } + } +} diff --git a/src/main/java/fr/istic/gm/weassert/test/exception/WeAssertException.java b/src/main/java/fr/istic/gm/weassert/test/exception/WeAssertException.java new file mode 100644 index 0000000..89d4a56 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/exception/WeAssertException.java @@ -0,0 +1,17 @@ +package fr.istic.gm.weassert.test.exception; + +/** + * The main application exception + */ +public class WeAssertException extends RuntimeException { + + public static final String WRONG_CLASS_PATH = "The class path is wrong."; + + public WeAssertException(String message) { + super(message); + } + + public WeAssertException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/fr/istic/gm/weassert/test/generator/AssertionGenerator.java b/src/main/java/fr/istic/gm/weassert/test/generator/AssertionGenerator.java new file mode 100644 index 0000000..a324080 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/generator/AssertionGenerator.java @@ -0,0 +1,9 @@ +package fr.istic.gm.weassert.test.generator; + +import fr.istic.gm.weassert.test.model.TestAnalysed; + +import java.util.List; + +public interface AssertionGenerator { + void generate(List testsAnalysed); +} diff --git a/src/main/java/fr/istic/gm/weassert/test/generator/CodeWriter.java b/src/main/java/fr/istic/gm/weassert/test/generator/CodeWriter.java new file mode 100644 index 0000000..653b5a7 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/generator/CodeWriter.java @@ -0,0 +1,9 @@ +package fr.istic.gm.weassert.test.generator; + +import java.util.List; + +public interface CodeWriter { + void insertOne(String methodName, String desc, String code); + void insertMany(String methodName, String desc, List codes); + void writeAndCloseFile(); +} diff --git a/src/main/java/fr/istic/gm/weassert/test/generator/impl/AssertionGeneratorImpl.java b/src/main/java/fr/istic/gm/weassert/test/generator/impl/AssertionGeneratorImpl.java new file mode 100644 index 0000000..1b4164a --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/generator/impl/AssertionGeneratorImpl.java @@ -0,0 +1,39 @@ +package fr.istic.gm.weassert.test.generator.impl; + +import fr.istic.gm.weassert.test.generator.AssertionGenerator; +import fr.istic.gm.weassert.test.generator.CodeWriter; +import fr.istic.gm.weassert.test.model.TestAnalysed; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class AssertionGeneratorImpl implements AssertionGenerator { + + private static final List PRIMITIVES = Arrays.asList(Integer.class, Long.class, Float.class, Double.class, Boolean.class); + private CodeWriter codeWriter; + + public AssertionGeneratorImpl(CodeWriter codeWriter) { + this.codeWriter = codeWriter; + } + + @Override + public void generate(List testsAnalysed) { + testsAnalysed.forEach(testAnalysed -> { + List generatedCodes = new ArrayList<>(); + + testAnalysed.getVariableValues().keySet().forEach(key -> { + Object o = testAnalysed.getVariableValues().get(key); + if (PRIMITIVES.contains(o.getClass())) { + generatedCodes.add("org.junit.Assert.assertEquals(" + key + "," + o.toString() + "); // GENERATED ASSERT"); + } else if (o.getClass() == String.class) { + generatedCodes.add("org.junit.Assert.assertEquals(" + key + ",\"" + o.toString() + "\"); // GENERATED ASSERT"); + } + }); + + this.codeWriter.insertMany(testAnalysed.getMethodName(), testAnalysed.getMethodDesc(), generatedCodes); + }); + + this.codeWriter.writeAndCloseFile(); + } +} diff --git a/src/main/java/fr/istic/gm/weassert/test/generator/impl/JavassitCodeWriter.java b/src/main/java/fr/istic/gm/weassert/test/generator/impl/JavassitCodeWriter.java new file mode 100644 index 0000000..83bda8f --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/generator/impl/JavassitCodeWriter.java @@ -0,0 +1,59 @@ +package fr.istic.gm.weassert.test.generator.impl; + +import fr.istic.gm.weassert.test.generator.CodeWriter; +import fr.istic.gm.weassert.test.exception.WeAssertException; +import javassist.CannotCompileException; +import javassist.ClassPool; +import javassist.CtClass; +import javassist.CtMethod; +import javassist.NotFoundException; +import lombok.Getter; + +import java.util.List; + +@Getter +public class JavassitCodeWriter implements CodeWriter { + private String className; + + private CtClass classContainer; + + public JavassitCodeWriter(Class clazz) { + ClassPool pool = ClassPool.getDefault(); + + try { + this.classContainer = pool.get(clazz.getName()); + this.className = clazz.getName(); + } catch (NotFoundException e) { + throw new WeAssertException( + String.format("CodeWriter: could not find class named %s", clazz.getName()), e + ); + } + } + + public void insertOne(String methodName, String desc, String code) { + try { + CtMethod method = this.classContainer.getMethod(methodName, desc); + method.insertAfter(code); + } catch (CannotCompileException e) { + throw new WeAssertException("JavassitCodeWriter: could not insert code", e); + } catch (NotFoundException e) { + throw new WeAssertException( + String.format("JavassitCodeWriter: could not find method named %s", methodName), e + ); + } + } + + @Override + public void insertMany(String methodName, String desc, List codes) { + codes.forEach(src -> this.insertOne(methodName, desc, src)); + } + + @Override + public void writeAndCloseFile() { + try { + this.classContainer.writeFile(); + } catch (Exception e) { + throw new WeAssertException("JavassitCodeWriter: could not write file", e); + } + } +} diff --git a/src/main/java/fr/istic/gm/weassert/test/generator/impl/SourceCodeWriter.java b/src/main/java/fr/istic/gm/weassert/test/generator/impl/SourceCodeWriter.java new file mode 100644 index 0000000..8a5d7b7 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/generator/impl/SourceCodeWriter.java @@ -0,0 +1,124 @@ +package fr.istic.gm.weassert.test.generator.impl; + +import fr.istic.gm.weassert.test.generator.CodeWriter; +import fr.istic.gm.weassert.test.exception.WeAssertException; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +@Slf4j +@Getter +public class SourceCodeWriter implements CodeWriter { + public static final String CLASSNAME_REGEX = "^(private |public |protected |)(static |)class (\\w+)( \\{|)$"; + + public static final String METHODNAME_REGEX = "^.*(@Test)(\\r|\\n)*\\s*(private |public |protected |)(static |)(void |)(\\w+)\\s*\\(\\)\\s+\\{$"; + + private File sourceFile; + + private String sourceCode; + + private String className; + + public SourceCodeWriter(String classPath) { + this.sourceFile = new File(classPath); + + if (!sourceFile.exists()) { + throw new WeAssertException(String.format("SourceCodeWriter: no such source file at: '%s'", classPath)); + } + + try { + this.sourceCode = new String(Files.readAllBytes(sourceFile.toPath())); + } catch (IOException e) { + throw new WeAssertException("SourceCodeWriter: could not read source code !"); + } + + findClassName(); + } + + private void findClassName() { + Pattern pattern = Pattern.compile(CLASSNAME_REGEX, Pattern.MULTILINE); + Matcher matcher = pattern.matcher(this.sourceCode); + + if (matcher.find()) { + if (matcher.group(3) != null) { + this.className = matcher.group(3); + } else { + throw new WeAssertException(getClassName() + ": could not find class name!"); + } + } else { + throw new WeAssertException(getClassName() + ": given file is not a Java class source file!"); + } + } + + public void insertOne(String methodName, String desc, String code) { + Pattern pattern = Pattern.compile(METHODNAME_REGEX, Pattern.MULTILINE); + Matcher matcher = pattern.matcher(this.sourceCode); + + int firstCurlyBraceIndex = 0; + while (matcher.find()) { + if (matcher.group(6).equals(methodName)) { + firstCurlyBraceIndex = matcher.end(); + break; + } + } + + if (firstCurlyBraceIndex == 0) { + throw new WeAssertException(String.format("%s: could not find method named \"%s\" !", getClassName(), methodName)); + } + + int lastCurlyBraceIndex = findLastCurlyBraceIndex(firstCurlyBraceIndex); + + StringBuilder str = new StringBuilder(this.sourceCode); + str.insert(lastCurlyBraceIndex, code + "\n"); + + // CODE INSERTION HERE + this.sourceCode = str.toString(); + } + + private int findLastCurlyBraceIndex(int firstCurlyIndex) { + int curlyBracesCounter = 1; + int lastCurlyBrace = firstCurlyIndex; + + for (;lastCurlyBrace < this.sourceCode.length(); lastCurlyBrace++) { + if (this.sourceCode.charAt(lastCurlyBrace) == '{') { + curlyBracesCounter++; + } else if (this.sourceCode.charAt(lastCurlyBrace) == '}') { + curlyBracesCounter--; + } + + if (curlyBracesCounter == 0) { + break; + } + } + + if(lastCurlyBrace == 0) { + throw new WeAssertException(getClassName() + ": parse error, could not find end of method!"); + } + + return lastCurlyBrace; + } + + @Override + public void insertMany(String methodName, String desc, List codes) { + codes.forEach(src -> this.insertOne(methodName, desc, src)); + } + + @Override + public void writeAndCloseFile() { + try { + PrintWriter writer = new PrintWriter(this.sourceFile); + writer.write(this.sourceCode); + writer.close(); + } catch (FileNotFoundException e) { + log.error("Can't find the file to write", e); + } + } +} diff --git a/src/main/java/fr/istic/gm/weassert/test/model/LocalVariableParsed.java b/src/main/java/fr/istic/gm/weassert/test/model/LocalVariableParsed.java new file mode 100644 index 0000000..fe6086e --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/model/LocalVariableParsed.java @@ -0,0 +1,19 @@ +package fr.istic.gm.weassert.test.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.List; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class LocalVariableParsed implements MethodSignature { + + private String name; + private String desc; + private List localVariables; +} diff --git a/src/main/java/fr/istic/gm/weassert/test/model/MethodDefinition.java b/src/main/java/fr/istic/gm/weassert/test/model/MethodDefinition.java new file mode 100644 index 0000000..5197631 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/model/MethodDefinition.java @@ -0,0 +1,19 @@ +package fr.istic.gm.weassert.test.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class MethodDefinition { + + private Class clazz; + + private String methodName; + + private String methodDesc; +} diff --git a/src/main/java/fr/istic/gm/weassert/test/model/MethodSignature.java b/src/main/java/fr/istic/gm/weassert/test/model/MethodSignature.java new file mode 100644 index 0000000..a8fb979 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/model/MethodSignature.java @@ -0,0 +1,6 @@ +package fr.istic.gm.weassert.test.model; + +public interface MethodSignature { + String getName(); + String getDesc(); +} diff --git a/src/main/java/fr/istic/gm/weassert/test/model/TestAnalysed.java b/src/main/java/fr/istic/gm/weassert/test/model/TestAnalysed.java new file mode 100644 index 0000000..5a6b326 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/model/TestAnalysed.java @@ -0,0 +1,33 @@ +package fr.istic.gm.weassert.test.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; + +import java.util.HashMap; +import java.util.Map; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +@EqualsAndHashCode(exclude = "variableValues") +public class TestAnalysed { + + private Class clazz; + + private String methodName; + + private String methodDesc; + + Map variableValues = new HashMap<>(); + + public Map getVariableValues() { + if (variableValues == null) { + variableValues = new HashMap<>(); + } + return variableValues; + } +} diff --git a/src/main/java/fr/istic/gm/weassert/test/model/VariableDefinition.java b/src/main/java/fr/istic/gm/weassert/test/model/VariableDefinition.java new file mode 100644 index 0000000..274e34a --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/model/VariableDefinition.java @@ -0,0 +1,21 @@ +package fr.istic.gm.weassert.test.model; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class VariableDefinition { + + private Class clazz; + + private String methodName; + + private String methodDesc; + + private String variableName; +} diff --git a/src/main/java/fr/istic/gm/weassert/test/runner/TestRunner.java b/src/main/java/fr/istic/gm/weassert/test/runner/TestRunner.java new file mode 100644 index 0000000..c18a4f7 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/runner/TestRunner.java @@ -0,0 +1,23 @@ +package fr.istic.gm.weassert.test.runner; + +import java.util.List; + +/** + * The test runner + */ +public interface TestRunner { + + /** + * Start all tests from the classes + * + * @param classes the classes to start test + */ + void startTests(List> classes); + + /** + * Start all tests from the class + * + * @param clazz the class to start test + */ + void startTest(Class clazz); +} diff --git a/src/main/java/fr/istic/gm/weassert/test/runner/TestRunnerListener.java b/src/main/java/fr/istic/gm/weassert/test/runner/TestRunnerListener.java new file mode 100644 index 0000000..58efcf8 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/runner/TestRunnerListener.java @@ -0,0 +1,6 @@ +package fr.istic.gm.weassert.test.runner; + +import org.junit.runner.notification.RunListener; + +public class TestRunnerListener extends RunListener { +} diff --git a/src/main/java/fr/istic/gm/weassert/test/runner/impl/TestRunnerImpl.java b/src/main/java/fr/istic/gm/weassert/test/runner/impl/TestRunnerImpl.java new file mode 100644 index 0000000..1752c5a --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/runner/impl/TestRunnerImpl.java @@ -0,0 +1,31 @@ +package fr.istic.gm.weassert.test.runner.impl; + +import fr.istic.gm.weassert.test.runner.TestRunner; +import lombok.extern.slf4j.Slf4j; +import org.junit.runner.JUnitCore; +import org.junit.runner.notification.RunListener; + +import java.util.List; + +@Slf4j +public class TestRunnerImpl implements TestRunner { + + private JUnitCore jUnitCore; + + public TestRunnerImpl(JUnitCore jUnitCore, RunListener runListener) { + this.jUnitCore = jUnitCore; + this.jUnitCore.addListener(runListener); + } + + @Override + public void startTests(List> classes) { + classes.forEach(this::startTest); + log.info("TESTS RUNNED: " + classes); + } + + @Override + public void startTest(Class clazz) { + jUnitCore.run(clazz); + log.info("TESTS RUNNED: " + clazz); + } +} diff --git a/src/main/java/fr/istic/gm/weassert/test/utils/BackupUtils.java b/src/main/java/fr/istic/gm/weassert/test/utils/BackupUtils.java new file mode 100644 index 0000000..7587aa3 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/utils/BackupUtils.java @@ -0,0 +1,5 @@ +package fr.istic.gm.weassert.test.utils; + +public interface BackupUtils { + void restore(); +} diff --git a/src/main/java/fr/istic/gm/weassert/test/utils/ClassReaderFactory.java b/src/main/java/fr/istic/gm/weassert/test/utils/ClassReaderFactory.java new file mode 100644 index 0000000..40b5657 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/utils/ClassReaderFactory.java @@ -0,0 +1,7 @@ +package fr.istic.gm.weassert.test.utils; + +import org.objectweb.asm.ClassReader; + +public interface ClassReaderFactory { + ClassReader create(Class clazz); +} diff --git a/src/main/java/fr/istic/gm/weassert/test/utils/ClassResolverUtil.java b/src/main/java/fr/istic/gm/weassert/test/utils/ClassResolverUtil.java new file mode 100644 index 0000000..7128605 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/utils/ClassResolverUtil.java @@ -0,0 +1,15 @@ +package fr.istic.gm.weassert.test.utils; + +public class ClassResolverUtil { + + private ClassResolverUtil() { + } + + public static String mapClassToClassPath(Class clazz) { + return String.format("%s%s.class", clazz.getProtectionDomain().getCodeSource().getLocation().getPath(), clazz.getName().replace(".", "/")); + } + + public static String mapClassToSourcePath(Class clazz) { + return String.format("%s%s.java", clazz.getProtectionDomain().getCodeSource().getLocation().getPath().replace("/target/classes/", "/src/main/java/").replace("/target/test-classes/", "/src/test/java/"), clazz.getName().replace(".", "/")); + } +} diff --git a/src/main/java/fr/istic/gm/weassert/test/utils/FileUtils.java b/src/main/java/fr/istic/gm/weassert/test/utils/FileUtils.java new file mode 100644 index 0000000..ecf0a13 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/utils/FileUtils.java @@ -0,0 +1,27 @@ +package fr.istic.gm.weassert.test.utils; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class FileUtils { + + private FileUtils() { + } + + public static List findFilesFromFolder(File folder) { + List files = new ArrayList<>(); + File[] listFiles = folder.listFiles(); + if (listFiles == null) { + return files; + } + for (File file : listFiles) { + if (file.isDirectory()) { + files.addAll(findFilesFromFolder(file)); + } else { + files.add(file); + } + } + return files; + } +} diff --git a/src/main/java/fr/istic/gm/weassert/test/utils/ProcessBuilderFactory.java b/src/main/java/fr/istic/gm/weassert/test/utils/ProcessBuilderFactory.java new file mode 100644 index 0000000..7b0a3e2 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/utils/ProcessBuilderFactory.java @@ -0,0 +1,6 @@ +package fr.istic.gm.weassert.test.utils; + +public interface ProcessBuilderFactory { + + ProcessBuilderWrapper create(String directory, String... command); +} diff --git a/src/main/java/fr/istic/gm/weassert/test/utils/ProcessBuilderWrapper.java b/src/main/java/fr/istic/gm/weassert/test/utils/ProcessBuilderWrapper.java new file mode 100644 index 0000000..2ccc2bb --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/utils/ProcessBuilderWrapper.java @@ -0,0 +1,11 @@ +package fr.istic.gm.weassert.test.utils; + +import java.io.File; +import java.util.List; + +public interface ProcessBuilderWrapper { + Process start(); + File directory(); + List command(); + ProcessBuilderWrapper directory(String filePath); +} diff --git a/src/main/java/fr/istic/gm/weassert/test/utils/UrlClassLoaderWrapper.java b/src/main/java/fr/istic/gm/weassert/test/utils/UrlClassLoaderWrapper.java new file mode 100644 index 0000000..11c4ea1 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/utils/UrlClassLoaderWrapper.java @@ -0,0 +1,18 @@ +package fr.istic.gm.weassert.test.utils; + +import java.util.List; + +/** + * A Url Class Loader wrapper to retrieve classes + */ +public interface UrlClassLoaderWrapper { + + /** + * Retrieve classes + * + * @return the classes + */ + List> getClassList(); + + void refresh(); +} diff --git a/src/main/java/fr/istic/gm/weassert/test/utils/impl/BackupUtilsImpl.java b/src/main/java/fr/istic/gm/weassert/test/utils/impl/BackupUtilsImpl.java new file mode 100644 index 0000000..3f6eb19 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/utils/impl/BackupUtilsImpl.java @@ -0,0 +1,37 @@ +package fr.istic.gm.weassert.test.utils.impl; + +import fr.istic.gm.weassert.test.exception.WeAssertException; +import fr.istic.gm.weassert.test.utils.BackupUtils; +import lombok.Getter; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; + +public class BackupUtilsImpl implements BackupUtils { + + private String fileName; + + @Getter + private byte[] content; + + public BackupUtilsImpl(String fileName) { + this.fileName = fileName; + File file = new File(fileName); + try { + content = Files.readAllBytes(file.toPath()); + } catch (IOException e) { + throw new WeAssertException("Can't store file: " + fileName, e); + } + } + + @Override + public void restore() { + File file = new File(fileName); + try { + Files.write(file.toPath(), content); + } catch (IOException e) { + throw new WeAssertException("Can't restore file: " + fileName, e); + } + } +} diff --git a/src/main/java/fr/istic/gm/weassert/test/utils/impl/ClassReaderFactoryImpl.java b/src/main/java/fr/istic/gm/weassert/test/utils/impl/ClassReaderFactoryImpl.java new file mode 100644 index 0000000..f5f9e3c --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/utils/impl/ClassReaderFactoryImpl.java @@ -0,0 +1,26 @@ +package fr.istic.gm.weassert.test.utils.impl; + +import fr.istic.gm.weassert.test.exception.WeAssertException; +import fr.istic.gm.weassert.test.utils.ClassReaderFactory; +import org.objectweb.asm.ClassReader; + +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; + +import static fr.istic.gm.weassert.test.exception.WeAssertException.WRONG_CLASS_PATH; +import static fr.istic.gm.weassert.test.utils.ClassResolverUtil.mapClassToClassPath; + +public class ClassReaderFactoryImpl implements ClassReaderFactory { + + @Override + public ClassReader create(Class clazz) { + try { + String classPath = mapClassToClassPath(clazz); + InputStream classInputStream = new FileInputStream(new File(classPath)); + return new ClassReader(classInputStream); + } catch (Exception e) { + throw new WeAssertException(WRONG_CLASS_PATH, e); + } + } +} diff --git a/src/main/java/fr/istic/gm/weassert/test/utils/impl/ProcessBuilderFactoryImpl.java b/src/main/java/fr/istic/gm/weassert/test/utils/impl/ProcessBuilderFactoryImpl.java new file mode 100644 index 0000000..7f1d652 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/utils/impl/ProcessBuilderFactoryImpl.java @@ -0,0 +1,27 @@ +package fr.istic.gm.weassert.test.utils.impl; + +import fr.istic.gm.weassert.test.exception.WeAssertException; +import fr.istic.gm.weassert.test.utils.ProcessBuilderFactory; +import fr.istic.gm.weassert.test.utils.ProcessBuilderWrapper; +import lombok.AllArgsConstructor; + +import java.nio.file.Paths; + +@AllArgsConstructor +public class ProcessBuilderFactoryImpl implements ProcessBuilderFactory { + + private static final String ERROR_DIRECTORY = "ProcessBuilderFactoryImpl: The directory needs to exists %s"; + private static final String ERROR_COMMAND = "ProcessBuilderFactoryImpl: No command"; + + @Override + public ProcessBuilderWrapper create(String directory, String... command) { + if (directory == null || !Paths.get(directory).toFile().isDirectory()) { + throw new WeAssertException(String.format(ERROR_DIRECTORY, directory)); + } + if (command == null || command.length == 0 || command[0].isEmpty()) { + throw new WeAssertException(ERROR_COMMAND); + } + return new ProcessBuilderWrapperImpl(command) + .directory(directory); + } +} diff --git a/src/main/java/fr/istic/gm/weassert/test/utils/impl/ProcessBuilderWrapperImpl.java b/src/main/java/fr/istic/gm/weassert/test/utils/impl/ProcessBuilderWrapperImpl.java new file mode 100644 index 0000000..840ef66 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/utils/impl/ProcessBuilderWrapperImpl.java @@ -0,0 +1,42 @@ +package fr.istic.gm.weassert.test.utils.impl; + +import fr.istic.gm.weassert.test.exception.WeAssertException; +import fr.istic.gm.weassert.test.utils.ProcessBuilderWrapper; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +public class ProcessBuilderWrapperImpl implements ProcessBuilderWrapper { + + private ProcessBuilder processBuilder; + + ProcessBuilderWrapperImpl(String... command) { + processBuilder = new ProcessBuilder(command); + } + + @Override + public Process start() { + try { + return processBuilder.start(); + } catch (IOException e) { + throw new WeAssertException("ProcessBuilderWrapperImpl: Process start failed", e); + } + } + + @Override + public File directory() { + return processBuilder.directory(); + } + + @Override + public List command() { + return processBuilder.command(); + } + + @Override + public ProcessBuilderWrapper directory(String filePath) { + processBuilder.directory(new File(filePath)); + return this; + } +} diff --git a/src/main/java/fr/istic/gm/weassert/test/utils/impl/UrlClassLoaderWrapperImpl.java b/src/main/java/fr/istic/gm/weassert/test/utils/impl/UrlClassLoaderWrapperImpl.java new file mode 100644 index 0000000..796f6f6 --- /dev/null +++ b/src/main/java/fr/istic/gm/weassert/test/utils/impl/UrlClassLoaderWrapperImpl.java @@ -0,0 +1,91 @@ +package fr.istic.gm.weassert.test.utils.impl; + +import fr.istic.gm.weassert.test.exception.WeAssertException; +import fr.istic.gm.weassert.test.utils.FileUtils; +import fr.istic.gm.weassert.test.utils.UrlClassLoaderWrapper; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; + +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +public class UrlClassLoaderWrapperImpl implements UrlClassLoaderWrapper { + + private static final String LOAD_CLASS_ERROR = "Can't load class: "; + private static final String PARSED_ERROR = "Url can't be parsed: "; + + private List paths; + + @Getter + private List> classList; + + private URLClassLoader urlClassLoader; + + public UrlClassLoaderWrapperImpl(List paths) { + this.paths = paths; + refresh(); + } + + @Override + public void refresh() { + log.info("LOADING CLASS..."); + URL[] urls = paths.stream().map(this::mapToUrl).toArray(URL[]::new); + urlClassLoader = URLClassLoader.newInstance(urls, getClass().getClassLoader()); + classList = mapPathsToClass(paths); + log.info("CLASSLOADED: " + paths); + } + + private List> mapPathsToClass(List paths) { + List fileNames = mapPathsToFileNames(paths); + List classNames = new ArrayList<>(); + fileNames.forEach(f -> { + for (String p : paths) { + if (f.startsWith(p)) { + String className = f.replaceAll(String.format("^%s", p), "") + .replaceAll("\\.class$", "") + .replaceAll("^/", "") + .replace("/", "."); + classNames.add(className); + break; + } + } + }); + return classNames.stream().map(this::mapToClass).collect(Collectors.toList()); + } + + private List mapPathsToFileNames(List paths) { + return paths.stream() + .map(File::new) + .map(FileUtils::findFilesFromFolder) + .flatMap(Collection::stream) + .map(File::getPath) + .filter(f -> f.endsWith(".class")).collect(Collectors.toList()); + } + + private URL mapToUrl(String a) { + try { + return new URL(String.format("file://%s/", a)); + } catch (MalformedURLException e) { + String message = PARSED_ERROR + e.getCause(); + log.info(message); + throw new WeAssertException(message, e); + } + } + + private Class mapToClass(String className) { + try { + return urlClassLoader.loadClass(className); + } catch (ClassNotFoundException e) { + String message = LOAD_CLASS_ERROR + e.getCause(); + log.info(message); + throw new WeAssertException(message, e); + } + } +} diff --git a/src/main/resources/rootPane.fxml b/src/main/resources/rootPane.fxml new file mode 100644 index 0000000..24e34e3 --- /dev/null +++ b/src/main/resources/rootPane.fxml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +