diff --git a/.gitignore b/.gitignore index 953523f..7917c72 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,6 @@ # Log file *.log -!sample.log # BlueJ files *.ctxt @@ -13,7 +12,8 @@ # Package Files # *.jar -!atos.jar +!evosuite-client-botsing-*.jar +!*projectA*.jar *.war *.nar *.ear @@ -30,6 +30,7 @@ hs_err_pid* .Rapp.history # Maven +dependency-reduced-pom.xml target/ # OS generated files # @@ -48,3 +49,7 @@ Thumbs.db .project .settings/ +# local test directory +**/test/**/botsing/local/ +**/test/**/botsing/model/generation/local/ + diff --git a/.travis.yml b/.travis.yml index 485df5a..30a43ee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,12 +3,13 @@ language: java jdk: - oraclejdk8 -install: true - script: mvn package checkstyle:check before_script: - mvn clean +before_install: + - mvn install:install-file -Dfile=botsing-reproduction/evosuite-client-botsing-1.0.7.jar -DgroupId=org.evosuite -DartifactId=evosuite-client-botsing -Dversion=1.0.7 -Dpackaging=jar + after_success: - - mvn jacoco:report coveralls:report \ No newline at end of file + - mvn jacoco:report coveralls:report diff --git a/LICENSE b/LICENSE.txt similarity index 100% rename from LICENSE rename to LICENSE.txt diff --git a/README.md b/README.md index b2fb912..2133ab4 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,54 @@ [![Build Status](https://travis-ci.org/STAMP-project/botsing.svg?branch=master)](https://travis-ci.org/STAMP-project/botsing) [![Coverage Status](https://coveralls.io/repos/github/STAMP-project/botsing/badge.svg?branch=master)](https://coveralls.io/github/STAMP-project/botsing?branch=master) +[![Maven Central](https://img.shields.io/maven-central/v/eu.stamp-project/botsing-reproduction.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22eu.stamp-project%22%20AND%20a:%22botsing-reproduction%22) Botsing is a Java framework for crash reproduction. It relies on [EvoSuite](http://www.evosuite.org) for code instrumentation. +## Usage + +### From Maven + +See the [documentation for the Maven Plugin](https://github.com/STAMP-project/botsing/tree/master/botsing-maven). + +### Command line interface + + +#### botsing reproduction +The latest version of Botsing command line (botsing-reproduction-X-X-X.jar) is available at [https://github.com/STAMP-project/botsing/releases](https://github.com/STAMP-project/botsing/releases). + +Botsing has three mandatory parameters: + - `-crash_log` the file with the stack trace. The stack trace should be clean (no error message) and cannot contain any nested exceptions. + - `-target_frame` the target frame to reproduce. This number should be between 1 and the number of frames in the stack trace. + - `-project_cp` the classpath of the project and all its dependencies. The classpath can be a folder containing all the `.jar` files required to run the software under test. + + +By default, Botsing uses the following parameter values: + - `-Dsearch_budget=1800`, a time budget of 30 min. This value can be modified by specifying an additional parameter in format `-Dsearch_budget=60` (here, for 60 seconds). + - `-Dpopulation=100`, a default population with 100 individuals. This value may be modified using `-Dpopulation=10` (here, for 10 individuals). + - `-Dtest_dir=crash-reproduction-tests`, the output directory where the tests will be created (if any test is generated). This value may be modified using `-Dtest_dir=newoutputdir`. + +To check the list of options, use: + +```sh +$ java -jar botsing-reproduction.jar -help +usage: java -jar botsing-reproduction.jar -crash_log stacktrace.log -target_frame 2 + + -project_cp dep1.jar;dep2.jar ) + -crash_log File with the stack trace + -D use value for given property + -help Prints this help message. + -project_cp classpath of the project under test and all its + dependencies + -target_frame Level of the target frame +``` + +#### Example + +``` +java -jar botsing-reproduction.jar -crash_log LANG-1b.log -target_frame 2 -project_cp ~/bin +``` + ## Contributing @@ -14,6 +59,18 @@ Botsing is licensed under Apache-2.0, pull request as are welcome. The coding style is described in [`checkstyle.xml`](checkstyle.xml). Please (successfully) run the command `mvn checkstyle:check` before submitting a pull request. +### Building Botsing + +Currently, Botsing using a customized version of the EvoSuite-client. Hence, the building process contains two steps: +1- Installing the customized version of EvoSuite-client: +``` +mvn install:install-file -Dfile=botsing-reproduction/evosuite-client-botsing-1.0.7.jar -DgroupId=org.evosuite -DartifactId=evosuite-client-botsing -Dversion=1.0.7 -Dpackaging=jar +``` +2- Build the Botsing project: + +``` +mvn package +``` ### Adding a dependency @@ -40,7 +97,50 @@ And referenced in the dependencies of the module using the following syntax: ``` Please check in the list of properties that the dependency version is not already there before adding a new one. +#### botsing preprocessing + +The latest version of Botsing command line (botsing-preprocessing-X-X-X.jar) is available at [https://github.com/STAMP-project/botsing/releases](https://github.com/STAMP-project/botsing/releases). + +Botsing preprocessing has these mandatory parameters: + - `-i` represents the input file path (`crash_log`) with the stack trace to clean. For example `-i=path-name-of-crash-log` + - `-o` represents the output file path (`output_log`) cleaned of the error message and/or nested exceptions. For example `-o=path-name-of-output-log` + +These parameters define actions to perform (clean) in the stack trace: + - `-f` to flatten the stack trace. + - `-e` to remove the error message. + + - `-p` to set the regexp in the stack trace use case. For example `-p=my.package.*` + +#### Example + +To clean the nested stack trace + +``` +java -jar botsing-preprocessing.jar -i=crash_log.txt -o=output_log.log -f -p=com.example.* +``` + +or to remove the error message + +``` +java -jar botsing-preprocessing.jar -e -i=crash_log.txt -o==output_log.log +``` + +Note that you can use also both actions (`-f` and `-e`). + +## Background + +Botsing (Dutch for 'crash') is a complete re-implementation of the crash replication tool [EvoCrash](http://www.evocrash.org) ([github](https://github.com/STAMP-project/EvoCrash)). +Whereas EvoCrash was a full clone of EvoSuite (making it hard to update EvoCrash as EvoSuite evolves), Botsing relies on EvoSuite as a (maven) dependency only. Furthermore, it comes with an extensive test suite, making it easier to extend. The license adopted is Apache, in order to facilitate adoption in industry and academia. + +The underlying evolutionary algorithm and fitness function are described in: + +* Mozhan Soltani, Annibale Panichella, and Arie van Deursen. Search-Based Crash Reproduction and Its Impact on Debugging. _IEEE Transactions on Software Engineering_, 2018. ([DOI](http://dx.doi.org/10.1109/TSE.2018.2877664), [preprint](https://pure.tudelft.nl/portal/en/publications/searchbased-crash-reproduction-and-its-impact-on-debugging(1281ce36-7afc-43d9-ad83-b69c60fbd49a).html)) + +* Mozhan Soltani, Pouria Derakhshanfar, Annibale Panichella, Xavier Devroey, Andy Zaidman, and Arie van Deursen. Single-objective versus Multi-Objectivized Optimization for Evolutionary Crash Reproduction. In Colanzi and McMinn, editors, _Search-Based Software Engineering - 10th International Symposium, SSBSE 2018 - Proceedings_. Lecture Notes in Computer Science, Springer. 2018. p. 325-340. ([DOI](http://dx.doi.org/10.1007/978-3-319-99241-9_18), [preprint](https://pure.tudelft.nl/portal/en/publications/singleobjective-versus-multiobjectivized-optimization-for-evolutionary-crash-reproduction(ccece8a1-79cd-4303-adca-34a920bf7d14).html)). + + ## Funding -Botsing is partially funded by research project STAMP (European Commission - H2020) -![STAMP - European Commission - H2020](https://github.com/STAMP-project/docs-forum/blob/master/docs/images/logo_readme_md.png) +Botsing is partially funded by research project STAMP (European Commission - H2020) ICT-16-10 No.731529. + +![STAMP - European Commission - H2020](docs/logo_readme_md.png) diff --git a/botsing-commons/pom.xml b/botsing-commons/pom.xml new file mode 100644 index 0000000..3ad8394 --- /dev/null +++ b/botsing-commons/pom.xml @@ -0,0 +1,22 @@ + + + + botsing + eu.stamp-project + 1.0.4-SNAPSHOT + + 4.0.0 + + botsing-commons + + + org.evosuite + evosuite-client-botsing + ${evosuite-client.version} + + + + + \ No newline at end of file diff --git a/botsing-commons/src/main/java/eu/stamp/botsing/commons/BotsingTestGenerationContext.java b/botsing-commons/src/main/java/eu/stamp/botsing/commons/BotsingTestGenerationContext.java new file mode 100644 index 0000000..da2760c --- /dev/null +++ b/botsing-commons/src/main/java/eu/stamp/botsing/commons/BotsingTestGenerationContext.java @@ -0,0 +1,36 @@ +package eu.stamp.botsing.commons; + +import eu.stamp.botsing.commons.instrumentation.InstrumentingClassLoader; + + +public class BotsingTestGenerationContext { + + private static final BotsingTestGenerationContext instance = new BotsingTestGenerationContext(); + + private InstrumentingClassLoader classLoader; + private ClassLoader originalClassLoader; + + private BotsingTestGenerationContext(){ + originalClassLoader = this.getClass().getClassLoader(); + classLoader = new InstrumentingClassLoader(); + } + + + public static BotsingTestGenerationContext getInstance() { + return instance; + } + + public void goingToExecuteSUTCode() { + + Thread.currentThread().setContextClassLoader(classLoader); + } + + + public void doneWithExecutingSUTCode() { + Thread.currentThread().setContextClassLoader(originalClassLoader); + } + + public InstrumentingClassLoader getClassLoaderForSUT() { + return classLoader; + } +} diff --git a/botsing-commons/src/main/java/eu/stamp/botsing/commons/instrumentation/BotsingBytecodeInstrumentation.java b/botsing-commons/src/main/java/eu/stamp/botsing/commons/instrumentation/BotsingBytecodeInstrumentation.java new file mode 100644 index 0000000..363e7de --- /dev/null +++ b/botsing-commons/src/main/java/eu/stamp/botsing/commons/instrumentation/BotsingBytecodeInstrumentation.java @@ -0,0 +1,87 @@ +package eu.stamp.botsing.commons.instrumentation; + +import org.evosuite.Properties; +import org.evosuite.assertion.CheapPurityAnalyzer; +import org.evosuite.graphs.cfg.CFGClassAdapter; +import org.evosuite.instrumentation.*; +import org.evosuite.runtime.RuntimeSettings; +import org.evosuite.runtime.instrumentation.*; +import org.evosuite.runtime.util.ComputeClassWriter; +import org.evosuite.seeding.PrimitiveClassAdapter; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.commons.SerialVersionUIDAdder; +import org.objectweb.asm.util.TraceClassVisitor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.PrintWriter; + +public class BotsingBytecodeInstrumentation { + + private static final Logger LOG = LoggerFactory.getLogger(BotsingBytecodeInstrumentation.class); + public byte[] transformBytes(ClassLoader classLoader, String className, ClassReader reader) { + + TransformationStatistics.reset(); + + int asmFlags = ClassWriter.COMPUTE_FRAMES; + ClassWriter writer = new ComputeClassWriter(asmFlags); + + ClassVisitor cv = writer; + if (LOG.isDebugEnabled()) { + cv = new TraceClassVisitor(cv, new PrintWriter(System.err)); + } + + if (Properties.RESET_STATIC_FIELDS) { + cv = new StaticAccessClassAdapter(cv, className); + } + + if (Properties.PURE_INSPECTORS) { + CheapPurityAnalyzer purityAnalyzer = CheapPurityAnalyzer.getInstance(); + cv = new PurityAnalysisClassVisitor(cv, className, purityAnalyzer); + } + + if (Properties.MAX_LOOP_ITERATIONS >= 0) { + cv = new LoopCounterClassAdapter(cv); + } + + cv = new RemoveFinalClassAdapter(cv); + + cv = new ExecutionPathClassAdapter(cv, className); + + cv = new CFGClassAdapter(classLoader, cv, className); + + // Collect constant values for the value pool + cv = new PrimitiveClassAdapter(cv, className); + + cv = handleStaticReset(className, cv); + + cv = new MethodCallReplacementClassAdapter(cv, className); + if(RuntimeSettings.applyUIDTransformation){ + cv = new SerialVersionUIDAdder(cv); + } + + reader.accept(cv, ClassReader.SKIP_FRAMES); + try{ + return writer.toByteArray(); + }catch (RuntimeException e){ + LOG.warn("Method code is too large"); + return new byte[0]; + } + + } + + private static ClassVisitor handleStaticReset(String className, ClassVisitor cv) { + cv = new CreateClassResetClassAdapter(cv, className, Properties.RESET_STATIC_FINAL_FIELDS); + // Adds a callback before leaving the method + cv = new EndOfClassInitializerVisitor(cv, className); + + return cv; + } + + + public static boolean checkIfCanInstrument(String className) { + return RuntimeInstrumentation.checkIfCanInstrument(className); + } +} diff --git a/botsing-commons/src/main/java/eu/stamp/botsing/commons/instrumentation/ClassInstrumentation.java b/botsing-commons/src/main/java/eu/stamp/botsing/commons/instrumentation/ClassInstrumentation.java new file mode 100644 index 0000000..3d34778 --- /dev/null +++ b/botsing-commons/src/main/java/eu/stamp/botsing/commons/instrumentation/ClassInstrumentation.java @@ -0,0 +1,34 @@ +package eu.stamp.botsing.commons.instrumentation; + +import eu.stamp.botsing.commons.BotsingTestGenerationContext; +import org.evosuite.Properties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +public class ClassInstrumentation { + private static final Logger LOG = LoggerFactory.getLogger(ClassInstrumentation.class); + public List instrumentClasses(List interestingClasses){ + List instrumentedClasses = new ArrayList<>(); + List instrumentedClassesName = new ArrayList<>(); + + for(String clazz: interestingClasses){ + if(instrumentedClassesName.contains(clazz)){ + continue; + } + LOG.debug("Instrumenting class "+ clazz); + Class cls; + try { + Properties.TARGET_CLASS=clazz; + cls = Class.forName(clazz,false, BotsingTestGenerationContext.getInstance().getClassLoaderForSUT()); + instrumentedClasses.add(cls); + instrumentedClassesName.add(clazz); + } catch (ClassNotFoundException | NoClassDefFoundError e) { + LOG.warn("Error in loading {}",clazz); + } + } + return instrumentedClasses; + } +} diff --git a/botsing-commons/src/main/java/eu/stamp/botsing/commons/instrumentation/InstrumentingClassLoader.java b/botsing-commons/src/main/java/eu/stamp/botsing/commons/instrumentation/InstrumentingClassLoader.java new file mode 100644 index 0000000..0f366e5 --- /dev/null +++ b/botsing-commons/src/main/java/eu/stamp/botsing/commons/instrumentation/InstrumentingClassLoader.java @@ -0,0 +1,110 @@ +package eu.stamp.botsing.commons.instrumentation; + +import eu.stamp.botsing.commons.BotsingTestGenerationContext; +import org.evosuite.classpath.ResourceList; +import org.evosuite.runtime.instrumentation.RuntimeInstrumentation; +import org.objectweb.asm.ClassReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +public class InstrumentingClassLoader extends ClassLoader { + private static final Logger LOG = LoggerFactory.getLogger(InstrumentingClassLoader.class); + private final Map> visitedClasses = new HashMap<>(); + + private final BotsingBytecodeInstrumentation instrumentation; + + private final ClassLoader classLoader; + public InstrumentingClassLoader() { + this(new BotsingBytecodeInstrumentation()); + } + + public InstrumentingClassLoader(BotsingBytecodeInstrumentation instrumentation) { + super(InstrumentingClassLoader.class.getClassLoader()); + classLoader = InstrumentingClassLoader.class.getClassLoader(); + this.instrumentation = instrumentation; + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + if (!RuntimeInstrumentation.checkIfCanInstrument(name)){ + Class result = visitedClasses.get(name); + if (result != null) { + return result; + } + result = classLoader.loadClass(name); + return result; + } + Class result = visitedClasses.get(name); + if (result != null) { + return result; + } else { + Class instrumentedClass = instrumentClass(name); + return instrumentedClass; + } + } + + private Class instrumentClass(String fullyQualifiedTargetClass)throws ClassNotFoundException { + String className = fullyQualifiedTargetClass.replace('.', '/'); + InputStream is = null; + try { + is = ResourceList.getInstance(BotsingTestGenerationContext.getInstance().getClassLoaderForSUT()).getClassAsStream(fullyQualifiedTargetClass); + if (is == null) { + LOG.warn("Class '" + className + ".class" + "' should be in target project!"); +// throw new ClassNotFoundException("Class '" + className + ".class" + "' should be in target project!"); + } + byte[] byteBuffer = getTransformedBytes(className,is); + createPackageDefinition(fullyQualifiedTargetClass); + try{ + Class result = defineClass(fullyQualifiedTargetClass, byteBuffer, 0,byteBuffer.length); + visitedClasses.put(fullyQualifiedTargetClass, result); + LOG.debug("Loaded class: " + fullyQualifiedTargetClass); + return result; + }catch(ClassFormatError cfe){ + return null; + } + + + + + + } catch (Throwable t) { + LOG.error("Error while loading class: "+t); +// throw new ClassNotFoundException(t.getMessage(), t); + return null; + } finally { + if(is != null){ + try { + is.close(); + } catch (IOException e) { + LOG.warn(e.getMessage()); + return null; + } + } + } + } + + + protected byte[] getTransformedBytes(String className, InputStream is) throws IOException { + return instrumentation.transformBytes(this, className, new ClassReader(is)); + } + + private void createPackageDefinition(String className){ + int i = className.lastIndexOf('.'); + // check if className is valid + if (i != -1) { + String packageName = className.substring(0, i); + // Check if package already loaded. + Package pkg = getPackage(className.substring(0, i)); + if(pkg==null){ + // If it is not loadeed we will define it to the classloder + definePackage(packageName, null, null, null, null, null, null, null); + LOG.debug("Defined package (3): "+getPackage(packageName)+", "+getPackage(packageName).hashCode()); + } + } + } +} \ No newline at end of file diff --git a/botsing-examples/LICENSE.txt b/botsing-examples/LICENSE.txt new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/botsing-examples/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/botsing-examples/pom.xml b/botsing-examples/pom.xml new file mode 100644 index 0000000..61b6acb --- /dev/null +++ b/botsing-examples/pom.xml @@ -0,0 +1,26 @@ + + + + botsing + eu.stamp-project + 1.0.4-SNAPSHOT + + 4.0.0 + + botsing-examples + + + + junit + junit + ${junit.version} + test + + + org.slf4j + slf4j-api + ${slf4j.version} + test + + + diff --git a/botsing-examples/src/main/java/eu/stamp/botsing/Fraction.java b/botsing-examples/src/main/java/eu/stamp/botsing/Fraction.java new file mode 100644 index 0000000..1259a02 --- /dev/null +++ b/botsing-examples/src/main/java/eu/stamp/botsing/Fraction.java @@ -0,0 +1,26 @@ +package eu.stamp.botsing; + +public class Fraction { + + int numerator; + int denominator; + + public Fraction(int numerator, int denominator){ + this.numerator = numerator; + this.denominator = denominator; + } + + public double getValue(){ + if (denominator == 0) { + throw new IllegalArgumentException(); + } + return ((double) this.numerator)/this.denominator; + } + + public double getShiftedValue(int shift){ + if (denominator == 0) { + throw new IllegalArgumentException(); + } + return this.numerator/(this.denominator + shift); + } +} diff --git a/botsing-examples/src/main/java/eu/stamp/botsing/PrivateFraction.java b/botsing-examples/src/main/java/eu/stamp/botsing/PrivateFraction.java new file mode 100644 index 0000000..ce42f5f --- /dev/null +++ b/botsing-examples/src/main/java/eu/stamp/botsing/PrivateFraction.java @@ -0,0 +1,16 @@ +package eu.stamp.botsing; + +public class PrivateFraction { + + + public static double getShiftedValue(int a, int b){ + if (b == 0) { + throw new IllegalArgumentException(); + } + return getValue(a,b); + } + + private static double getValue(int a , int b){ + return a/(b+1); + } +} diff --git a/botsing-examples/src/main/resources/Fraction.log b/botsing-examples/src/main/resources/Fraction.log new file mode 100644 index 0000000..7eb84b4 --- /dev/null +++ b/botsing-examples/src/main/resources/Fraction.log @@ -0,0 +1,2 @@ +java.lang.ArithmeticException: / by zero + at eu.stamp.botsing.Fraction.getShiftedValue(Fraction.java:24) \ No newline at end of file diff --git a/botsing-examples/src/main/resources/PrivateFraction.log b/botsing-examples/src/main/resources/PrivateFraction.log new file mode 100644 index 0000000..dbc47f1 --- /dev/null +++ b/botsing-examples/src/main/resources/PrivateFraction.log @@ -0,0 +1,3 @@ +java.lang.ArithmeticException: / by zero + at eu.stamp.botsing.PrivateFraction.getValue(PrivateFraction.java:14) + at eu.stamp.botsing.PrivateFraction.getShiftedValue(PrivateFraction.java:10) \ No newline at end of file diff --git a/botsing-examples/src/main/resources/sample_dep/projectA-1.0-SNAPSHOT-tests.jar b/botsing-examples/src/main/resources/sample_dep/projectA-1.0-SNAPSHOT-tests.jar new file mode 100644 index 0000000..19550aa Binary files /dev/null and b/botsing-examples/src/main/resources/sample_dep/projectA-1.0-SNAPSHOT-tests.jar differ diff --git a/botsing-examples/src/main/resources/sample_dep/projectA-1.0-SNAPSHOT.jar b/botsing-examples/src/main/resources/sample_dep/projectA-1.0-SNAPSHOT.jar new file mode 100644 index 0000000..b4c1271 Binary files /dev/null and b/botsing-examples/src/main/resources/sample_dep/projectA-1.0-SNAPSHOT.jar differ diff --git a/botsing-examples/src/test/java/eu/stamp/botsing/FractionTest.java b/botsing-examples/src/test/java/eu/stamp/botsing/FractionTest.java new file mode 100644 index 0000000..8fbb8c7 --- /dev/null +++ b/botsing-examples/src/test/java/eu/stamp/botsing/FractionTest.java @@ -0,0 +1,58 @@ +package eu.stamp.botsing; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.Assert.assertEquals; + +public class FractionTest { + + private static final Logger LOG = LoggerFactory.getLogger(FractionTest.class); + + @Rule + public TestRule watcher = new TestWatcher() { + @Override + protected void starting(Description description) { + LOG.info(String.format("Starting test: %s()...", + description.getMethodName())); + } + }; + + @Test (expected = java.lang.ArithmeticException.class) + public void getShiftedValue_crash() { + Fraction f = new Fraction(1,2); + f.getShiftedValue(-2); + } + + @Test (expected = java.lang.IllegalArgumentException.class) + public void getValue_zeroCase(){ + Fraction f = new Fraction(1,0); + f.getValue(); + } + + @Test + public void getValue(){ + Fraction f = new Fraction(1,2); + double value = f.getValue(); + assertEquals (0.5, value, 0.0000001); + } + + @Test + public void getShiftedValue(){ + Fraction f = new Fraction(1,2); + double value = f.getShiftedValue(1); + assertEquals (0.0, value, 0.0000001); + } + + @Test (expected = java.lang.IllegalArgumentException.class) + public void getShiftedValue_zero(){ + Fraction f = new Fraction(1,0); + double value = f.getShiftedValue(1); + } + +} \ No newline at end of file diff --git a/botsing-examples/src/test/java/eu/stamp/botsing/PrivateFractionTest.java b/botsing-examples/src/test/java/eu/stamp/botsing/PrivateFractionTest.java new file mode 100644 index 0000000..80c49e1 --- /dev/null +++ b/botsing-examples/src/test/java/eu/stamp/botsing/PrivateFractionTest.java @@ -0,0 +1,24 @@ +package eu.stamp.botsing; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class PrivateFractionTest { + + @Test + public void getShiftedValue() { + double value = PrivateFraction.getShiftedValue(6,1); + assertEquals(3, value, 0.0001); + } + + @Test (expected = IllegalArgumentException.class) + public void getShiftedValue_zero() { + double value = PrivateFraction.getShiftedValue(6,0); + } + + @Test (expected = ArithmeticException.class) + public void getShiftedValue_crash() { + double value = PrivateFraction.getShiftedValue(6,-1); + } +} \ No newline at end of file diff --git a/botsing-maven/LICENSE.txt b/botsing-maven/LICENSE.txt new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/botsing-maven/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/botsing-maven/README.md b/botsing-maven/README.md new file mode 100644 index 0000000..9239ba5 --- /dev/null +++ b/botsing-maven/README.md @@ -0,0 +1,24 @@ +# Botsing Maven plugin + +Botsing Maven plugin let's you run [Botsing](https://github.com/STAMP-project/botsing) inside your Maven project as a Maven plugin. + +## Install + +You can install it in your Maven local repository following this steps: + +1. From the project folder compile the project running `mvn compile` + +2. Install it in your Maven local repository running `mvn install` + +## Usage + +If you have a Maven project you can run Botsing using Maven from the project folder with this command: + +``` +mvn eu.stamp-project:botsing-maven:botsing -Dcrash_log=ACC-474/ACC-474.log -Dtarget_frame=2 +``` + +* crash_log is the parameter to tell Botsing where is the log file to analyze. +* target_frame is the parameter to tell Botsing how many lines of the log to replicate + +To have more information on the parameters that you can use, please refer to the [Botsing project](https://github.com/STAMP-project/botsing). \ No newline at end of file diff --git a/botsing-maven/pom.xml b/botsing-maven/pom.xml index 0b617bb..a41e601 100644 --- a/botsing-maven/pom.xml +++ b/botsing-maven/pom.xml @@ -1,23 +1,102 @@ - - - botsing - eu.stamp-project - 0.0.1-SNAPSHOT - - 4.0.0 - - botsing-maven - The maven plugin for crash reproduction using botsing. - - - - eu.stamp-project - botsing-reproduction - ${project.version} - - + + + botsing + eu.stamp-project + 1.0.4-SNAPSHOT + + 4.0.0 + botsing-maven + maven-plugin + Botsing Maven Plugin + The Maven plugin for crash reproduction using Botsing. + + + + Luca Andreatta + + + + + + eu.stamp-project + botsing-reproduction + ${project.version} + + + + org.apache.maven + maven-plugin-api + ${maven-plugin-api.version} + + + + org.apache.maven.plugin-tools + maven-plugin-annotations + ${maven-plugin-annotations.version} + provided + + + + org.apache.maven.plugin-testing + maven-plugin-testing-harness + ${maven-plugin-testing-harness.version} + + + + org.apache.maven + maven-compat + ${maven-compat.version} + + + + org.apache.maven + maven-core + ${maven-core.version} + + + + org.apache.maven + maven-model + ${maven-model.version} + + + + org.apache.maven + maven-aether-provider + ${maven-aether-provider.version} + + + + junit + junit + ${junit.version} + test + + + + + + + + org.apache.maven.plugins + maven-plugin-plugin + 3.5 + + + default-descriptor + process-classes + + + help-goal + + helpmojo + + + + + + + \ No newline at end of file diff --git a/botsing-maven/src/main/java/eu/stamp/botsing/BotsingMojo.java b/botsing-maven/src/main/java/eu/stamp/botsing/BotsingMojo.java new file mode 100644 index 0000000..6edcca1 --- /dev/null +++ b/botsing-maven/src/main/java/eu/stamp/botsing/BotsingMojo.java @@ -0,0 +1,180 @@ +package eu.stamp.botsing; + +import java.io.File; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +import org.apache.maven.artifact.Artifact; +import org.apache.maven.plugin.AbstractMojo; +import org.apache.maven.plugin.MojoExecutionException; +import org.apache.maven.plugins.annotations.Component; +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.project.MavenProject; +import org.eclipse.aether.RepositorySystem; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.artifact.DefaultArtifact; +import org.eclipse.aether.repository.RemoteRepository; +import org.eclipse.aether.resolution.ArtifactRequest; +import org.eclipse.aether.resolution.ArtifactResolutionException; +import org.eclipse.aether.resolution.ArtifactResult; + +/** + * Mojo class to run Botsing + * + * @author Luca Andreatta + */ +@Mojo(name = "botsing", defaultPhase = LifecyclePhase.VERIFY, requiresDependencyResolution = ResolutionScope.TEST, threadSafe = true) +public class BotsingMojo extends AbstractMojo { + + /** + * To see all the properties available take a look at org.evosuite.Properties.java + */ + + /** + * Folder with dependencies to run the project + */ + @Parameter(property = "project_cp") + private String projectCP; + + /** + * Log file with the stacktrace + */ + @Parameter(defaultValue = "sample.log", property = "crash_log") + private String crashLog; + + /** + * The frame level up to which parse the stack trace + */ + @Parameter(defaultValue = "3", property = "target_frame") + private Integer targetFrame; + + /** + * Maven variables + */ + @Parameter(defaultValue = "${project}", required = true, readonly = true) + private MavenProject project; + + @Component + private RepositorySystem repoSystem; + + @Parameter(defaultValue = "${repositorySystemSession}", readonly = true, required = true) + private RepositorySystemSession repoSession; + + @Parameter(defaultValue = "${project.remoteProjectRepositories}", readonly = true, required = true) + private List repositories; + + @Override + public void execute() throws MojoExecutionException { + getLog().info("Starting Botsing to generate tests with EvoSuite"); + Botsing botsing = new Botsing(); + List propertiesList = new ArrayList(); + + propertiesList.add("-"+CommandLineParameters.CRASH_LOG_OPT); + propertiesList.add(crashLog); + + propertiesList.add("-"+CommandLineParameters.TARGET_FRAME_OPT); + propertiesList.add(targetFrame.toString()); + + String dependencies = null; + if (projectCP != null) { + dependencies = getDependenciesFromFolder(projectCP); + } else { + dependencies = getDependenciesFromPom(); + } + + getLog().debug("dependencies: " + dependencies); + propertiesList.add("-"+CommandLineParameters.PROJECT_CP_OPT); + propertiesList.add(dependencies); + + try { + // Start Botsing + botsing.parseCommandLine(propertiesList.toArray(new String[0])); + + } catch (Exception e) { + throw new MojoExecutionException("Error executing Botsing", e); + } + + getLog().info("Stopping Botsing"); + } + + public String getDependenciesFromPom() throws MojoExecutionException { + String result = ""; + + // Add ./target/classes + result += project.getModel().getBuild().getDirectory() + File.separator + "classes" + File.pathSeparator; + + // Add pom project dependencies + for (Artifact unresolvedArtifact : this.project.getDependencyArtifacts()) { + File file = getArtifactFile(unresolvedArtifact); + + result += file.getAbsolutePath() + File.pathSeparator; + } + + return result; + } + + private File getArtifactFile(Artifact artifact) throws MojoExecutionException { + /** + * Taken from https://gist.github.com/vincent-zurczak/282775f56d27e12a70d3 + */ + + // We ask Maven to resolve the artifact's location. + // It may imply downloading it from a remote repository, + // searching the local repository or looking into the reactor's cache. + + // To achieve this, we must use Aether + // (the dependency mechanism behind Maven). + String artifactId = artifact.getArtifactId(); + org.eclipse.aether.artifact.Artifact aetherArtifact = new DefaultArtifact(artifact.getGroupId(), + artifact.getArtifactId(), artifact.getClassifier(), artifact.getType(), artifact.getVersion()); + + ArtifactRequest req = new ArtifactRequest().setRepositories(this.repositories).setArtifact(aetherArtifact); + ArtifactResult resolutionResult; + try { + resolutionResult = this.repoSystem.resolveArtifact(this.repoSession, req); + + } catch (ArtifactResolutionException e) { + throw new MojoExecutionException("Artifact " + artifactId + " could not be resolved.", e); + } + + // The file should exists, but we never know. + File file = resolutionResult.getArtifact().getFile(); + if (file == null || !file.exists()) { + getLog().warn("Artifact " + artifactId + + " has no attached file. Its content will not be copied in the target model directory."); + } + + return file; + } + + public static String getDependenciesFromFolder(String dependenciesFolder) { + String result = ""; + + if (dependenciesFolder == null) { + return result; + } + + File depFolder = new File(dependenciesFolder); + File[] listOfFilesInSourceFolder = depFolder.listFiles(); + + for (int i = 0; i < listOfFilesInSourceFolder.length; i++) { + + if (listOfFilesInSourceFolder[i].getName().charAt(0) != '.') { + Path depPath = Paths.get(depFolder.getAbsolutePath(), listOfFilesInSourceFolder[i].getName()); + String dependency = depPath.toString(); + + result += (dependency + File.pathSeparator); + } + } + + result = result.substring(0, result.length() - 1); + + return result; + } + +} \ No newline at end of file diff --git a/botsing-model-generation/LICENSE.txt b/botsing-model-generation/LICENSE.txt new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/botsing-model-generation/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/botsing-model-generation/pom.xml b/botsing-model-generation/pom.xml new file mode 100644 index 0000000..a1a662c --- /dev/null +++ b/botsing-model-generation/pom.xml @@ -0,0 +1,91 @@ + + + + botsing + eu.stamp-project + 1.0.4-SNAPSHOT + + 4.0.0 + + botsing-model-generation + + + junit + junit + ${junit.version} + + + org.hamcrest + java-hamcrest + ${hamcrest.version} + test + + + org.mockito + mockito-core + ${mockito.version} + test + + + commons-cli + commons-cli + ${commons-cli.version} + + + com.google.code.gson + gson + ${gson.version} + + + org.evosuite + evosuite-client-botsing + ${evosuite-client.version} + + + be.unamur.info + yami-tool + ${yami.version} + + + org.slf4j + log4j-slf4j-impl + + + + + eu.stamp-project + botsing-commons + ${project.version} + + + + + + + + maven-assembly-plugin + ${maven-assembly-plugin.version} + + + jar-with-dependencies + + + + eu.stamp.botsing.model.generation.Main + + + + + + make-my-jar-with-dependencies + package + + single + + + + + + + + \ No newline at end of file diff --git a/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/CommandLineParameters.java b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/CommandLineParameters.java new file mode 100644 index 0000000..df3e401 --- /dev/null +++ b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/CommandLineParameters.java @@ -0,0 +1,75 @@ +package eu.stamp.botsing.model.generation; + +/*- + * #%L + * botsing-reproduction + * %% + * Copyright (C) 2017 - 2018 eu.stamp-project + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; + +public class CommandLineParameters { + + public static final String PROJECT_CP_OPT = "projectCP"; + public static final String HELP_OPT = "help"; + public static final String PROJECT_PREFIX = "projectPrefix"; + public static final String OUTPUT_FOLDER = "outDir"; + public static final String PROJECT_PACKAGE = "projectPackage"; + public static final String CRASHES = "crashes"; + + public static Options getCommandLineOptions() { + Options options = new Options(); + // Classpath + options.addOption(Option.builder(PROJECT_CP_OPT) + .hasArg() + .desc("Classpath of the project under test and all its dependencies") + .build()); + // Help message + options.addOption(Option.builder(HELP_OPT) + .desc("Prints this help message.") + .build()); + + //if all of the interesting classes has the same prefix, the user can use this option + options.addOption(Option.builder(PROJECT_PREFIX) + .hasArg() + .desc("Prefix of the classes that we want to use for manual/dynamic analysis") + .build()); + + //if all of the interesting classes has the same package name, the user can use this option + options.addOption(Option.builder(PROJECT_PACKAGE) + .hasArg() + .desc("Package name of the classes that we want to use for manual/dynamic analysis") + .build()); + + // note: Either projectPrefix or projectPackage should be set. + + // User can limit the number of tests for dynamic analysis by passing the crashes + options.addOption(Option.builder(CRASHES) + .hasArg() + .desc("Limiting the number of tests for dynamic analysis by passing the crashes. The format of this parameter should be a list off crash logs addresses with JSON format.") + .build()); + + options.addOption(Option.builder(OUTPUT_FOLDER) + .hasArg() + .desc("the output directory.") + .build()); + + return options; + } + +} diff --git a/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/Main.java b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/Main.java new file mode 100644 index 0000000..6ccac1e --- /dev/null +++ b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/Main.java @@ -0,0 +1,102 @@ +package eu.stamp.botsing.model.generation; + +import be.yami.exception.SessionBuildException; +import com.google.gson.Gson; +import eu.stamp.botsing.model.generation.callsequence.CallSequenceCollector; +import eu.stamp.botsing.model.generation.callsequence.CallSequencesPoolManager; +import eu.stamp.botsing.model.generation.helper.LogReader; +import eu.stamp.botsing.model.generation.model.ModelGenerator; +import org.apache.commons.cli.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.ArrayList; + +public class Main { + + private static final Logger LOG = LoggerFactory.getLogger(Main.class); + + @SuppressWarnings("checkstyle:systemexit") + public static void main(String[] args) { + Main botsingModelGeneration = new Main(); + botsingModelGeneration.parseCommandLine(args); + System.exit(0); + } + + public void parseCommandLine(String[] args) { + Options options = CommandLineParameters.getCommandLineOptions(); + CommandLine commands = parseCommands(args, options); + if (commands.hasOption(CommandLineParameters.HELP_OPT)){ + printHelpMessage(options); + }else if(commands.hasOption(CommandLineParameters.PROJECT_CP_OPT)){ // Generate the model for passed cps + CallSequenceCollector callSequenceCollector; + File file = new File(commands.getOptionValue(CommandLineParameters.PROJECT_CP_OPT)); + if(file.isDirectory()) { + File[] jarsFiles = file.listFiles((File f) -> f.isFile() && f.getName().endsWith(".jar")); + String[] jarsCp = new String[jarsFiles.length]; + for (int i = 0; i < jarsCp.length; i++) { + jarsCp[i] = jarsFiles[i].getAbsolutePath(); + } + callSequenceCollector = new CallSequenceCollector(jarsCp); + }else{ + callSequenceCollector = new CallSequenceCollector(commands.getOptionValue(CommandLineParameters.PROJECT_CP_OPT)); + } + + String outputFolder = commands.hasOption(CommandLineParameters.OUTPUT_FOLDER)?commands.getOptionValue(CommandLineParameters.OUTPUT_FOLDER):"generated_results"; + + ArrayList involvedObejcts = new ArrayList<>(); + if(commands.hasOption(CommandLineParameters.CRASHES)){ + Gson gson = new Gson(); + ArrayList crashes = gson.fromJson(commands.getOptionValue(CommandLineParameters.CRASHES), ArrayList.class); + LogReader logReader= new LogReader(crashes); + involvedObejcts = logReader.collectInvolvedObjects(); + } + + // set project prefix + if (commands.hasOption(CommandLineParameters.PROJECT_PREFIX) ^ commands.hasOption(CommandLineParameters.PROJECT_PACKAGE)) { + if(commands.hasOption(CommandLineParameters.PROJECT_PREFIX)){ + callSequenceCollector.collect(commands.getOptionValue(CommandLineParameters.PROJECT_PREFIX), outputFolder, involvedObejcts, true); + }else{ + callSequenceCollector.collect(commands.getOptionValue(CommandLineParameters.PROJECT_PACKAGE), outputFolder,involvedObejcts, false); + } + + // Here, we have the list of call sequences. We just need to pass it to the yami tool + ModelGenerator modelGenerator = new ModelGenerator(); + + try { + modelGenerator.generate(CallSequencesPoolManager.getInstance().getPool(),Paths.get(outputFolder, "models").toString()); + } catch (IOException e) { + e.printStackTrace(); + } catch (SessionBuildException e) { + e.printStackTrace(); + } + + }else{ + LOG.error("Either project prefix or project package name should be passed as an input. For more information -> help"); + } + }else{ + LOG.error("Project classpath should be passed as an input. For more information -> help"); + } + } + + + protected CommandLine parseCommands(String[] args, Options options){ + CommandLineParser parser = new DefaultParser(); + CommandLine commands = null; + try { + commands = parser.parse(options, args); + } catch (ParseException e) { + LOG.error("Could not parse command line!", e); + printHelpMessage(options); + return null; + } + return commands; + } + + private void printHelpMessage(Options options) { + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("java -jar botsing_model_generator.jar -projectCP dep1.jar;dep2.jar )", options); + } +} diff --git a/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/analysis/classpath/CPAnalysor.java b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/analysis/classpath/CPAnalysor.java new file mode 100644 index 0000000..ef480d3 --- /dev/null +++ b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/analysis/classpath/CPAnalysor.java @@ -0,0 +1,33 @@ +package eu.stamp.botsing.model.generation.analysis.classpath; + + +import org.evosuite.setup.InheritanceTree; +import org.evosuite.setup.InheritanceTreeGenerator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + + +public class CPAnalysor { + private static final Logger LOG = LoggerFactory.getLogger(CPAnalysor.class); + + private static InheritanceTree inheritanceTree = null; + + public static void analyzeClass( List classPath) throws RuntimeException{ + initInheritanceTree(classPath); + } + + private static void initInheritanceTree(List classPath) { + LOG.info("Calculate inheritance hierarchy"+classPath.toString()); + inheritanceTree = InheritanceTreeGenerator.createFromClassPath(classPath); + InheritanceTreeGenerator.gatherStatistics(inheritanceTree); + } + + + public static InheritanceTree getInheritanceTree(){ + return inheritanceTree; + } + +} + diff --git a/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/analysis/sourcecode/StaticAnalyser.java b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/analysis/sourcecode/StaticAnalyser.java new file mode 100644 index 0000000..df97690 --- /dev/null +++ b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/analysis/sourcecode/StaticAnalyser.java @@ -0,0 +1,251 @@ +package eu.stamp.botsing.model.generation.analysis.sourcecode; + +import eu.stamp.botsing.commons.BotsingTestGenerationContext; +import eu.stamp.botsing.model.generation.callsequence.CallSequencesPoolManager; +import eu.stamp.botsing.model.generation.callsequence.MethodCall; +import org.evosuite.graphs.GraphPool; +import org.evosuite.graphs.cfg.BytecodeInstruction; +import org.evosuite.graphs.cfg.RawControlFlowGraph; +import org.evosuite.junit.CoverageAnalysis; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; + +public class StaticAnalyser { + private static final Logger LOG = LoggerFactory.getLogger(StaticAnalyser.class); + + + private BytecodeInstruction oldBC = null; + private String oldBCObject = null; + private String oldBCBranch = null; + + private LinkedList testSuite = new LinkedList<>(); + private Map> objectsTests = new HashMap<>(); + + +// private Map>> exportedMethodCalls = new HashMap>>(); + + + public void analyse(List interestingClasses) { + int counter = 0; + for (String clazz : interestingClasses) { + try{ + if (clazz.startsWith("org.xwiki.rendering.wikimodel.internal.common.javacc")){ + continue; + } + counter++; + LOG.info("Analyzing methods of class " + clazz + " ("+counter+"/"+interestingClasses.size()+")"); + boolean isTest = false; + Class cls = null; + try { + cls = Class.forName(clazz,false, BotsingTestGenerationContext.getInstance().getClassLoaderForSUT()); + if(CoverageAnalysis.isTest(cls)){ + LOG.info("The class {} is a testSuite",clazz); + isTest=true; + testSuite.add(clazz); + } + } catch (ClassNotFoundException | NoClassDefFoundError e) { + // e.printStackTrace(); + LOG.warn("error in loading {}",clazz); + } + + + GraphPool graphPool = GraphPool.getInstance(BotsingTestGenerationContext.getInstance().getClassLoaderForSUT()); + Map methodsGraphs = graphPool.getRawCFGs(clazz); + if (methodsGraphs != null) { + for (Map.Entry entry : methodsGraphs.entrySet()) { + Map>> collectedCallSequencesForCurrentMethod = analyseMethod(clazz, entry.getKey(), entry.getValue(),isTest); + savingMethodCallSequences(collectedCallSequencesForCurrentMethod); + } + } else { + LOG.warn("The generated control flow graphs for class {} was empty. We cannot execute manual analysis withour the control flow graph.", clazz); + } + }catch(Exception e){ + LOG.warn("Error in analyzing class {}",clazz); + LOG.warn(e.toString()); + } + } + } + + private void savingMethodCallSequences(Map>> methodCallSequences) { + for (Map.Entry>> CSEntry : methodCallSequences.entrySet()) { + String clazz = CSEntry.getKey(); + Map> sequences = CSEntry.getValue(); + for (Map.Entry> callSequenceEntry : sequences.entrySet()) { + List callSequence = callSequenceEntry.getValue(); + CallSequencesPoolManager.getInstance().addSequence(clazz, callSequence); + } + } + } + + private Map>> analyseMethod(String className, String methodname, RawControlFlowGraph cfg, boolean isTest) { + LOG.info("Reading Call Sequences from method " + methodname); + clearOldBC(); + Map>> callSequencesOfCurrentMethod = new HashMap>>(); + List bcList = cfg.determineMethodCalls(); + for (BytecodeInstruction bc : bcList) { + LOG.debug("analyzing byteCode " + bc.explain()); + String calledMethodsClass = bc.getCalledMethodsClass(); + if (!calledMethodsClass.equals(className) && !bc.toString().contains("evosuite")) {// Filter the internal method calls + + if (isTest){ + if (!objectsTests.containsKey(calledMethodsClass)){ + objectsTests.put(calledMethodsClass,new LinkedList()); + } + if (!objectsTests.get(calledMethodsClass).contains(className)){ + objectsTests.get(calledMethodsClass).add(className); + } + } + + if (bc.isConstructorInvocation()) { + // Here, we should instantiate a new call sequence + handleConstructorInvocation(bc, cfg, callSequencesOfCurrentMethod); + } else if (bc.isCallToStaticMethod() || bc.toString().contains("INVOKESTATIC")) { + // Here, we have a call to a static method call. It means we may need a new call sequence. + handleStaticMethodCallInvocation(bc, callSequencesOfCurrentMethod); + } else { + // Here, we have a regular method call. Also, We are sure that we do not need to initialize a new call sequence of call here. We should just add this to an existing call sequence. + handleRegularMethodInvocation(bc, cfg, callSequencesOfCurrentMethod); + } + } else { + LOG.debug("The bytecode Instruction is filtered."); + clearOldBC(); + } + } + return callSequencesOfCurrentMethod; + } + + private void handleRegularMethodInvocation(BytecodeInstruction bc, RawControlFlowGraph cfg, Map>> callSequencesOfCurrentMethod) throws IllegalStateException{ + if (bc.getSourceOfMethodInvocationInstruction() != null) { + if (bc.getSourceOfMethodInvocationInstruction().getVariableName() != null) { + if (bc.getSourceOfMethodInvocationInstruction().getVariableName().length() > 0) { + // We have a variable for the object which is used for method invocation + String variableName = bc.getSourceOfMethodInvocationInstruction().getVariableName(); + // We should have the type of the object in this slot + String parentType = detectParentType(variableName, callSequencesOfCurrentMethod); + recordRegularInvocation(bc, parentType, variableName, callSequencesOfCurrentMethod); + } else { + LOG.warn("The variable name is empty: {} ", bc); +// throw new IllegalStateException("Variable with empty name discovered during static analysis for instruction: " + bc); + } + + } else { + if (bc.getSourceOfMethodInvocationInstruction().equals(this.oldBC)) { + // This method is invoked by the returned value of the previous method call. So, it should be store in the same call sequence. + recordInvocation(bc, this.oldBCObject, oldBCBranch, callSequencesOfCurrentMethod); + } else { + LOG.error("The variable name of the current byteCode instruction is missing: " + bc.toString()); + } + } + } else { + LOG.warn("Following regular method call cannot find its source of method invocation: " + bc.toString()); + } + } + + private void recordRegularInvocation(BytecodeInstruction bc, String parentType, String variableName, Map>> callSequencesOfCurrentMethod) throws IllegalStateException { + if (parentType == null || (parentType.equals(bc.getCalledMethodsClass()))) { + // parent is initialized outside of the CUT. For instance, System.out variable in printing. + recordInvocation(bc, bc.getCalledMethodsClass(), variableName, callSequencesOfCurrentMethod); + } else if (callSequencesOfCurrentMethod.get(parentType).containsKey(variableName)) { + // The object is initialized by another class. We will use this type for recording this invocation. + recordInvocation(bc, parentType, variableName, callSequencesOfCurrentMethod); + } else { + LOG.error("Cannot detect the right call sequence for recording: {}", bc); +// throw new IllegalStateException("Could not detect the right call sequence ofr recording " + bc); + } + } + + private String detectParentType(String branch, Map>> callSequencesOfCurrentMethod) { + String parentType = null; + for (Map.Entry>> CSEntry : callSequencesOfCurrentMethod.entrySet()) { + Map> sequences = CSEntry.getValue(); + if (sequences.containsKey(branch)) { + // We found our type. We must achieve to this branch + parentType = CSEntry.getKey(); +// LOG.info("parent type " + parentType); + break; + } + } + return parentType; + } + + private void handleStaticMethodCallInvocation(BytecodeInstruction bc, Map>> callSequencesOfCurrentMethod) { + LOG.debug("Detect a static call. Adding it to the static call sequence."); + recordInvocation(bc, bc.getCalledMethodsClass(), "static", callSequencesOfCurrentMethod); + } + + private void handleConstructorInvocation(BytecodeInstruction bc, RawControlFlowGraph cfg, Map>> callSequencesOfCurrentMethod) { + String newVariableName = getNewVariableName(bc, cfg); + if (newVariableName.length() > 0) { + // Here, we need to add a new call sequence to the methodCallSequences + recordInvocation(bc, bc.getCalledMethodsClass(), newVariableName, callSequencesOfCurrentMethod); + } else { + LOG.debug("Could not find a new variable. It is not a call sequence."); + } + + } + + private void recordInvocation(BytecodeInstruction bc, String clazz, String branch, Map>> callSequencesOfCurrentMethod) { + + // Check if we already had this class in this method + if (!callSequencesOfCurrentMethod.containsKey(clazz)) { // If we did not see it, we would add its key to methodCallSequences + Map> newSequence = new HashMap>(); + callSequencesOfCurrentMethod.put(clazz, newSequence); + LOG.debug("add new class to callSequencesOfCurrentMethod: " + clazz); + } + + + // Check if we already had this branch in the selected class + if (!callSequencesOfCurrentMethod.get(clazz).containsKey(branch)) { + callSequencesOfCurrentMethod.get(clazz).put(branch, new LinkedList()); + LOG.debug("add new branch to class {}: {}", clazz, branch); + } + + // Add the call sequence + callSequencesOfCurrentMethod.get(clazz).get(branch).add(new MethodCall(bc)); + LOG.debug("Add a new method call to a call sequence. Class: {}, Branch: {}, bc: {}", clazz, branch, bc); + + setOldBC(bc, clazz, branch); + + } + + private String getNewVariableName(BytecodeInstruction bc, RawControlFlowGraph cfg) { + String newVariableName = ""; // If this value remains unchanged, we will not do anything + + // Try to change newVariableSlot + int id = bc.getInstructionId() + 1; + BytecodeInstruction next = cfg.getInstruction(id); + if (next.getInstructionType().equals("ASTORE")) { + // Only here we change the newVariableSlot + newVariableName = next.getVariableName(); + } else { + if (!next.getInstructionType().equals("ALOAD")) { + LOG.warn("The returned value did not stored: "+next.explain()); + } + } + return newVariableName; + } + + + private void setOldBC(BytecodeInstruction bc, String objectName, String branchName) { + oldBC = bc; + oldBCBranch = branchName; + oldBCObject = objectName; + } + + private void clearOldBC() { + oldBC = null; + oldBCBranch = null; + oldBCObject = null; + } + + + public LinkedList getTestSuite(){ + return this.testSuite; + } + + public Map> getObjectsTests(){ + return this.objectsTests; + } +} diff --git a/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/analysis/testcases/DynamicAnalyser.java b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/analysis/testcases/DynamicAnalyser.java new file mode 100644 index 0000000..cc32bd5 --- /dev/null +++ b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/analysis/testcases/DynamicAnalyser.java @@ -0,0 +1,103 @@ +package eu.stamp.botsing.model.generation.analysis.testcases; + +import eu.stamp.botsing.model.generation.callsequence.CallSequencesPoolManager; +import eu.stamp.botsing.model.generation.callsequence.MethodCall; +import eu.stamp.botsing.model.generation.testcase.carving.CarvingManager; +import org.evosuite.Properties; +import org.evosuite.testcase.TestCase; +import org.evosuite.testcase.statements.Statement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + + +public class DynamicAnalyser { + private static final Logger LOG = LoggerFactory.getLogger(DynamicAnalyser.class); + + public void analyse(Map> objectsTests, ArrayList involvedObjects){ + List testSuites = new ArrayList(); + if(involvedObjects.size()>0){ + for(String involvedObj: involvedObjects){ + if(objectsTests.containsKey(involvedObj)){ + for(String candidateTS: objectsTests.get(involvedObj)){ + try { + testSuites.add(candidateTS); + }catch (Exception e){ + LOG.warn("Error in adding testsuite {}",candidateTS); + LOG.warn(e.toString()); + } + } + } + } + }else{ + for (Map.Entry> entry : objectsTests.entrySet()){ + for(String test: entry.getValue()){ + if(!testSuites.contains(test)){ + try{ + testSuites.add(test); + }catch (Exception e){ + LOG.warn("Error in adding testsuite {}",test); + LOG.warn(e.toString()); + } + } + } + } + } + + + if(testSuites.size()>0){ + CarvingManager manager = CarvingManager.getInstance(); + Properties.SELECTED_JUNIT = String.join(":", testSuites); + Map, List> carvedTestCases = manager.getCarvedTestCases(); + LOG.info("Test carving is finished."); + LOG.info("Collecting the call sequences."); + savingMethodCallSequences(carvedTestCases); + }else{ + LOG.info("No test suite detected for dynamic analysis!"); + } + // Execute the test cases in the detected test suite, and run the dynamic analysis on them. + } + + private void savingMethodCallSequences(Map, List> carvedTestCases){ + for (Class targetClass : carvedTestCases.keySet()) { + String className = targetClass.getName(); + for (TestCase test : carvedTestCases.get(targetClass)) { + for (List callSequence : getCallSequences(test)){ + CallSequencesPoolManager.getInstance().addSequence(className,callSequence); + } + } + } + } + + private List> getCallSequences(TestCase test) { + List> result = new LinkedList<>(); + boolean observedInit = false; + + List callSequence = new LinkedList(); + for (int i = 0; i < test.size(); i++) { + Statement statement = test.getStatement(i); + if(validForCallSequence(statement)){ + if (statement.getAccessibleObject().isConstructor()){ + if (observedInit){ + result.add(callSequence); + callSequence = new LinkedList(); + }else{ + observedInit = true; + } + } + callSequence.add(new MethodCall(statement)); + } + } + result.add(callSequence); + return result; + } + + private boolean validForCallSequence(Statement statement) { + return statement.getClass().getSimpleName().equals("MethodStatement") || statement.getClass().getSimpleName().equals("ConstructorStatement"); + } + +} diff --git a/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/callsequence/CallSequenceCollector.java b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/callsequence/CallSequenceCollector.java new file mode 100644 index 0000000..c83f212 --- /dev/null +++ b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/callsequence/CallSequenceCollector.java @@ -0,0 +1,99 @@ +package eu.stamp.botsing.model.generation.callsequence; + + +import eu.stamp.botsing.model.generation.analysis.classpath.CPAnalysor; +import eu.stamp.botsing.model.generation.analysis.sourcecode.StaticAnalyser; +import eu.stamp.botsing.model.generation.analysis.testcases.DynamicAnalyser; +import eu.stamp.botsing.model.generation.testusage.TestUsagePoolManager; +import org.evosuite.classpath.ClassPathHacker; +import org.evosuite.classpath.ClassPathHandler; +import org.evosuite.setup.InheritanceTree; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.*; + + +public class CallSequenceCollector { + private static final Logger LOG = LoggerFactory.getLogger(CallSequenceCollector.class); + + private String[] projectClassPaths; + public CallSequenceCollector(String cp){ + projectClassPaths=cp.split(File.pathSeparator); + } + public CallSequenceCollector(String[] jarsCp ){ + projectClassPaths = jarsCp.clone(); + } + + + + + + StaticAnalyser staticAnalyser = new StaticAnalyser(); + DynamicAnalyser dynamicAnalyser = new DynamicAnalyser(); + + public void collect(String targetClassIndicator, String outputFolder,ArrayList involvedObejcts, Boolean isPrefix){ + + //pre-processes before starting the analysis + if(projectClassPaths == null){ + LOG.error("Project classpath should be set before the model generation."); + } + // Class path handler + handleClassPath(); + + // Static Analysis + List interestingClasses = detectInterestingClasses(targetClassIndicator,isPrefix); +// generateCFGS(); + staticAnalyser.analyse(interestingClasses); + + // Dynamic Analysis + dynamicAnalyser.analyse(staticAnalyser.getObjectsTests(),involvedObejcts); + + // Storing the object usage of test suites to the output directory + TestUsagePoolManager.getInstance().savingTestsUsages(Paths.get(outputFolder,"carvedTests").toString()); + // Storing the collected call sequences + CallSequencesPoolManager.getInstance().report(); + } + + + private void handleClassPath() { + ClassPathHandler.getInstance().changeTargetClassPath(projectClassPaths); + List cpList = Arrays.asList(projectClassPaths); + for (String cp: cpList){ + try { + ClassPathHacker.addFile(cp); + } catch (IOException e) { + e.printStackTrace(); + } + } + CPAnalysor.analyzeClass(cpList); + } + + + + private List detectInterestingClasses(String targetClassIndicator,Boolean isPrefix) { + List interestingClasses = new ArrayList(); + InheritanceTree projectTree = CPAnalysor.getInheritanceTree(); + if(isPrefix){ + for (String clazz: projectTree.getAllClasses()){ + if (clazz.startsWith(targetClassIndicator)){ + interestingClasses.add(clazz); + } + } + }else{ + for (String clazz: projectTree.getAllClasses()){ + if (clazz.contains("."+targetClassIndicator+".")){ + interestingClasses.add(clazz); + } + } + } + return interestingClasses; + } + + + + +} diff --git a/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/callsequence/CallSequencesPool.java b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/callsequence/CallSequencesPool.java new file mode 100644 index 0000000..789e2a5 --- /dev/null +++ b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/callsequence/CallSequencesPool.java @@ -0,0 +1,16 @@ +package eu.stamp.botsing.model.generation.callsequence; + +import java.io.Serializable; +import java.util.*; + +public class CallSequencesPool implements Serializable { + protected Map>> pool = new HashMap>>(); + + public void addSequence(String clazz, List sequences) { + if (!pool.containsKey(clazz)) { + pool.put(clazz, new HashSet>()); + } + + pool.get(clazz).add(sequences); + } +} \ No newline at end of file diff --git a/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/callsequence/CallSequencesPoolManager.java b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/callsequence/CallSequencesPoolManager.java new file mode 100644 index 0000000..d0fe63e --- /dev/null +++ b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/callsequence/CallSequencesPoolManager.java @@ -0,0 +1,64 @@ +package eu.stamp.botsing.model.generation.callsequence; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.util.*; + +public class CallSequencesPoolManager extends CallSequencesPool { + private static final Logger LOG = LoggerFactory.getLogger(CallSequencesPoolManager.class); + + private static CallSequencesPoolManager instance = null; + private CallSequencesPoolManager() {} + + + public static CallSequencesPoolManager getInstance() { + if(instance == null) { + instance = new CallSequencesPoolManager(); + } + return instance; + } + + + + public void report(){ + for (Map.Entry>> entry : this.pool.entrySet()) { + String clazz = entry.getKey(); + Set> callSequences = entry.getValue(); + LOG.info("^^^^^^^^^^^^^^^^^"); + LOG.info("Exported call sequences for class "+ clazz); + LOG.info("Number of call sequences "+ callSequences.size()); + int counter = 1; + for (List callSequence:callSequences){ + LOG.info("================="); + LOG.info("call sequences #"+ counter); + for(MethodCall methodCall: callSequence){ + LOG.info(methodCall.getMethodName()+" - "+java.util.Arrays.toString(methodCall.getParams())); + } + counter++; + } + } + } + + + public void savePool(String outputPath){ + Gson gson = new GsonBuilder().create(); + String json = gson.toJson(this.pool,pool.getClass()); + + try (PrintWriter out = new PrintWriter(outputPath)) { + out.println(json); + } catch (FileNotFoundException e) { + LOG.error("The output directory is not valid."); + } + } + + + public Map>> getPool(){ + return this.pool; + } + + +} diff --git a/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/callsequence/MethodCall.java b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/callsequence/MethodCall.java new file mode 100644 index 0000000..b0a8424 --- /dev/null +++ b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/callsequence/MethodCall.java @@ -0,0 +1,48 @@ +package eu.stamp.botsing.model.generation.callsequence; + +import org.evosuite.graphs.cfg.BytecodeInstruction; +import org.evosuite.testcase.statements.Statement; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.Type; + + +public class MethodCall{ + private String[] params; + private String methodName; + private static final Logger LOG = LoggerFactory.getLogger(MethodCall.class); + + public MethodCall(Statement statement){ + if (statement.getAccessibleObject().isMethod()){ + methodName = statement.getAccessibleObject().getName(); + } else if (statement.getAccessibleObject().isConstructor()){ + methodName = ""; + } + + Type[] types = statement.getAccessibleObject().getGenericParameterTypes(); + params = new String[types.length]; + for (int i=0;i logs; + private ArrayList involvedObjects = new ArrayList<>(); + public LogReader(ArrayList logs){ + this.logs = logs; + } + + + public ArrayList collectInvolvedObjects() { + if(involvedObjects.size() == 0){ + parseObjects(); + } + return involvedObjects; + } + + private void parseObjects() { + for(String log: logs){ + try { + BufferedReader reader = readFromFile(log); + reader.readLine(); + + while(true) { + String tempFrame = reader.readLine(); + if (tempFrame== null || !tempFrame.contains("at")) { + break; + } + String detectedObj = fetchObject(tempFrame); + if(!involvedObjects.contains(detectedObj)){ + involvedObjects.add(detectedObj); + } + } + + }catch (FileNotFoundException e) { + LOG.warn("Stack trace file not found!", e); +// throw new IllegalArgumentException("Stack trace file not found!", e); + } catch (IOException e) { + LOG.info("IO Exception", e); + } catch (Exception e){ + LOG.info("Other exceptions happened", e); + } + } + } + + private String fetchObject(String tempFrame) { + LOG.info(tempFrame); + int startPoint = tempFrame.indexOf("at ") + 3; + String usefulPart = tempFrame.substring(startPoint); + int splitPoint = usefulPart.indexOf("("); + String usefulForOtherParts = usefulPart.substring(0, splitPoint); + String[] split = usefulForOtherParts.split("\\."); + // class detection + String clazz = String.join(".", Arrays.copyOfRange(split, 0, split.length - 1)); + return clazz; + } + + + private BufferedReader readFromFile(String filePath) throws FileNotFoundException { + File file = new File(filePath); + BufferedReader br = new BufferedReader(new FileReader(file)); + return br; + } +} diff --git a/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/model/ModelGenerator.java b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/model/ModelGenerator.java new file mode 100644 index 0000000..e9655c5 --- /dev/null +++ b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/model/ModelGenerator.java @@ -0,0 +1,69 @@ +package eu.stamp.botsing.model.generation.model; + +import be.vibes.dsl.io.Xml; +import be.vibes.ts.UsageModel; +import be.yami.exception.ModelGenerationException; +import be.yami.exception.SessionBuildException; +import be.yami.java.ClassMethodParametersKeyGenerator; +import be.yami.java.JsonMethodCallsSequenceBuilder; +import be.yami.java.MethodCallSequence; +import be.yami.java.MultipleModelsProcessor; +import be.yami.ngram.Bigram; +import be.yami.ngram.NGram; +import com.google.common.collect.Lists; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import eu.stamp.botsing.model.generation.callsequence.MethodCall; +import org.apache.commons.io.IOUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class ModelGenerator { + private static final Logger LOG = LoggerFactory.getLogger(ModelGenerator.class); + public void generate(Map>> pool,String outputFolder) throws IOException, SessionBuildException { + Gson gson = new GsonBuilder().create(); + String json = gson.toJson(pool,pool.getClass()); + // The bigram which will construct the model + final MultipleModelsProcessor processor = new MultipleModelsProcessor() { + @Override + protected NGram buildNewNGram(String name) { + LOG.info("Bigram created for class {}", name); + return new Bigram<>(name, ClassMethodParametersKeyGenerator.getInstance()); + } + }; + + JsonMethodCallsSequenceBuilder builder = JsonMethodCallsSequenceBuilder.newInstance(); + builder.addListener(processor); + + final List sizes = Lists.newArrayList(); + builder.addListener((MethodCallSequence seq) -> { + sizes.add(seq.size()); + LOG.info("Sequences processed: {}", sizes.size()); + }); + + InputStream in = IOUtils.toInputStream(json, "UTF-8"); + builder.buildSessions(in); + File outFolder = new File(outputFolder.replace(".JSON", "").replace(".Json", "")); + LOG.info("Printing models in folder {}", outFolder); + if (!outFolder.exists()) { + outFolder.mkdirs(); + } + processor.getNGrams().forEach((ngram) -> { + try { + LOG.info("Printing model {}", ngram.getName()); + UsageModel um = ngram.getModel(); + File output = new File(outFolder, ngram.getName() + ".xml"); + Xml.print(um, output); + } catch (ModelGenerationException ex) { + LOG.error("Exception while retrieving usage model for {}!", ngram.getName(), ex); + } + }); + } +} diff --git a/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/testcase/carving/CarvingManager.java b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/testcase/carving/CarvingManager.java new file mode 100644 index 0000000..f252ff2 --- /dev/null +++ b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/testcase/carving/CarvingManager.java @@ -0,0 +1,106 @@ +package eu.stamp.botsing.model.generation.testcase.carving; + +import org.evosuite.Properties; +import org.evosuite.classpath.ResourceList; +import org.evosuite.testcarver.capture.FieldRegistry; +import org.evosuite.testcase.TestCase; +import org.junit.runner.JUnitCore; +import org.junit.runner.Result; +import org.junit.runner.notification.Failure; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; + +public class CarvingManager { + private static final Logger LOG = LoggerFactory.getLogger(CarvingManager.class); + + private static CarvingManager instance = null; + + private boolean carvingFinished = false; + + private Map, List> carvedTestCases = new LinkedHashMap<>(); + + private CarvingManager(){} + + public static CarvingManager getInstance(){ + if(instance == null){ + instance = new CarvingManager(); + } + + return instance; + } + + public Map, List> getCarvedTestCases(){ + if (!carvingFinished) { + carveTestCases(); + } + + return carvedTestCases; + } + + private void carveTestCases() { + List testSuites = getListOftestSuites(); + // run test suites + final org.evosuite.testcarver.extraction.CarvingClassLoader classLoader = new org.evosuite.testcarver.extraction.CarvingClassLoader(); + final List> junitTestClasses = new ArrayList>(); + + final JUnitCore runner = new JUnitCore(); + final CarvingRunListener listener = new CarvingRunListener(); + runner.addListener(listener); + // Set carving class loader + FieldRegistry.carvingClassLoader = classLoader; + + for (String testSuiteName : testSuites) { + + try { + String classNameWithDots = ResourceList.getClassNameFromResourcePath(testSuiteName); + final Class junitClass = classLoader.loadClass(classNameWithDots); + if(junitClass != null){ + junitTestClasses.add(junitClass); + } + } catch (Exception e) { + LOG.error("Failed to load JUnit test class {}: {}", testSuiteName, e); + } + } + try{ + final Class[] classes = new Class[junitTestClasses.size()]; + junitTestClasses.toArray(classes); + Result result = runner.run(classes); + carvedTestCases = listener.getTestCases(); + + + LOG.info("Result: {}/{}", result.getFailureCount(), result.getRunCount()); + for(Failure failure : result.getFailures()) { + LOG.info("Failure: {}", failure.getMessage()); + LOG.info("Exception: {}", failure.getException()); + } + }catch (Exception e){ + LOG.warn("exception in final step of carving: {}",e.toString()); + }catch (Error e){ + LOG.warn("error in final step of carving: {}",e.toString()); + } + + + + + + } + + private List getListOftestSuites() { + List testSuites = new LinkedList<>(); + String selectedJunitProp = Properties.SELECTED_JUNIT; + if (selectedJunitProp == null || selectedJunitProp.trim().isEmpty()){ + LOG.warn( + "Properties.SELECTED_JUNIT is empty. test carving is failed."); + } + for (String testSuiteCP: selectedJunitProp.split(":")){ + testSuites.add(testSuiteCP.trim()); + } + + + return testSuites; + } + + +} diff --git a/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/testcase/carving/CarvingRunListener.java b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/testcase/carving/CarvingRunListener.java new file mode 100644 index 0000000..d2f44ef --- /dev/null +++ b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/testcase/carving/CarvingRunListener.java @@ -0,0 +1,112 @@ +package eu.stamp.botsing.model.generation.testcase.carving; + +import eu.stamp.botsing.commons.BotsingTestGenerationContext; +import eu.stamp.botsing.commons.instrumentation.BotsingBytecodeInstrumentation; +import eu.stamp.botsing.model.generation.testusage.TestUsagePoolManager; +import org.evosuite.Properties; +import org.evosuite.testcarver.capture.CaptureLog; +import org.evosuite.testcarver.capture.Capturer; +import org.evosuite.testcarver.codegen.CaptureLogAnalyzer; +import org.evosuite.testcarver.testcase.CarvedTestCase; +import org.evosuite.testcarver.testcase.EvoTestCaseCodeGenerator; +import org.evosuite.testcase.TestCase; +import org.evosuite.utils.generic.GenericTypeInference; +import org.junit.runner.Description; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.*; + +public class CarvingRunListener extends org.evosuite.testcarver.extraction.CarvingRunListener { + private static final Logger LOG = LoggerFactory.getLogger(CarvingRunListener.class); + private final Map, List> carvedTests = new LinkedHashMap<>(); + @Override + public void testFinished(Description description) { + try{ + final CaptureLog log = Capturer.stopCapture(); + LOG.info("Carving test {}.{}", description.getClassName(), description.getMethodName()); + List> observedClasses = this.processLog(description, log); + for(Class clazz : observedClasses){ + TestUsagePoolManager.getInstance().addTest(clazz.getName(), description.getClassName()); + } + + Capturer.clear(); + }catch (Exception e){ + LOG.warn("error in capturing log: {}",e.toString()); + } + + } + + + private List> processLog(Description description, final CaptureLog log) { + LOG.debug("Current log: "+log); + List> observedClasses = getObservedClasses(log); + + final CaptureLogAnalyzer analyzer = new CaptureLogAnalyzer(); + final EvoTestCaseCodeGenerator codeGen = new EvoTestCaseCodeGenerator(); + for(Class observedClass : observedClasses) { + Class[] targetClasses = new Class[1]; + targetClasses[0] = observedClass; + if(!carvedTests.containsKey(observedClass)){ + carvedTests.put(observedClass, new ArrayList()); + } + + analyzer.analyze(log, codeGen, targetClasses); + CarvedTestCase carvedTest = (CarvedTestCase) codeGen.getCode(); + + if(carvedTest == null) { + LOG.debug("Failed to carve test for "+Arrays.asList(targetClasses)); + codeGen.clear(); + continue; + } + + carvedTest.setName(description.getMethodName()); + + try { + carvedTest.changeClassLoader(BotsingTestGenerationContext.getInstance().getClassLoaderForSUT()); + GenericTypeInference inference = new GenericTypeInference(); + inference.inferTypes(carvedTest); + + carvedTests.get(observedClass).add(carvedTest); + }catch (Throwable t) { + LOG.info("Exception during carving: " + t); + for(StackTraceElement elem : t.getStackTrace()) { + LOG.info(elem.toString()); + } + LOG.info(carvedTest.toCode()); + } + codeGen.clear(); + } + return observedClasses; + } + + + private List> getObservedClasses(final CaptureLog log){ + List> observedClasses = new ArrayList<>(); + String[] testSuitePaths = Properties.SELECTED_JUNIT.split(":"); + List jUnitClassesNames = new ArrayList<>(); + for (String s : testSuitePaths) { + jUnitClassesNames.add(s.trim()); + } + Set uniqueObservedClasses = new LinkedHashSet(log.getObservedClasses()); + + for(String className : uniqueObservedClasses) { + if (!jUnitClassesNames.contains(className) && BotsingBytecodeInstrumentation.checkIfCanInstrument(className)) { + try { + Class clazz = Class.forName(className, true, BotsingTestGenerationContext.getInstance().getClassLoaderForSUT()); + if(!observedClasses.contains(clazz)){ + observedClasses.add(clazz); + } + } catch(ClassNotFoundException e) { + LOG.info("Error in instrumenting class "+className+" after carving: "+e); + } + } + } + return observedClasses; + } + + @Override + public Map, List> getTestCases() { + return carvedTests; + } + +} diff --git a/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/testcase/execution/TestExecutor.java b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/testcase/execution/TestExecutor.java new file mode 100644 index 0000000..165e6f9 --- /dev/null +++ b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/testcase/execution/TestExecutor.java @@ -0,0 +1,142 @@ +package eu.stamp.botsing.model.generation.testcase.execution; + +import eu.stamp.botsing.commons.BotsingTestGenerationContext; +import org.evosuite.Properties; +import org.evosuite.ga.stoppingconditions.MaxTestsStoppingCondition; +import org.evosuite.runtime.Runtime; +import org.evosuite.runtime.sandbox.PermissionStatistics; +import org.evosuite.runtime.sandbox.Sandbox; +import org.evosuite.runtime.util.JOptionPaneInputs; +import org.evosuite.runtime.util.SystemInUtil; +import org.evosuite.testcase.TestCase; +import org.evosuite.testcase.execution.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; + +public class TestExecutor implements ThreadFactory { + private static final Logger LOG = LoggerFactory.getLogger(TestExecutor.class); + + public static final String BOTSING_TEST_EXECUTION_THREAD_GROUP = "Botsing_Test_Execution_Group"; + public static final String BOTSING_TEST_EXECUTION_THREAD = "BOTSING_TEST_EXECUTION_THREAD"; + + private Thread currentThread; + + private final Set stalledThreads = new HashSet(); + private ThreadGroup threadGroup = null; + + private Set observers; + + private ExecutorService executor; + + private static TestExecutor instance = null; + + public volatile int threadCounter; + + private TestExecutor() { + executor = Executors.newSingleThreadExecutor(this); + newObservers(); + } + + public static synchronized TestExecutor getInstance() { + if (instance == null){ + instance = new TestExecutor(); + } + + return instance; + } + + + public ExecutionResult execute(TestCase tc, int timeout) { + Scope scope = new Scope(); + ExecutionResult result = execute(tc, scope, timeout); + return result; + } + + private ExecutionResult execute(TestCase tc, Scope scope, int timeout){ + ExecutionTracer.getExecutionTracer().clear(); + resetObservers(); + ExecutionObserver.setCurrentTest(tc); + MaxTestsStoppingCondition.testExecuted(); + Runtime.getInstance().resetRuntime(); + + TestRunnable callableTest = new TestRunnable(tc, scope, observers); + callableTest.storeCurrentThreads(); + + TimeoutHandler handler = new TimeoutHandler(); + try { + ExecutionResult result = null; + SystemInUtil.getInstance().initForTestCase(); + JOptionPaneInputs.getInstance().initForTestCase(); + + Sandbox.goingToExecuteSUTCode(); + BotsingTestGenerationContext.getInstance().goingToExecuteSUTCode(); + try { + result = handler.execute(callableTest, executor, timeout, Properties.CPU_TIMEOUT); + } finally { + Sandbox.doneWithExecutingSUTCode(); + BotsingTestGenerationContext.getInstance().doneWithExecutingSUTCode(); + } + + return result; + + }catch (Exception e){ + LOG.warn("Exception during executing the generated tests for class loading"); + return null; + } + } + + + + private void resetObservers() { + for (ExecutionObserver observer : observers) { + observer.clear(); + } + } + + @Override + public Thread newThread(Runnable r) { + if (currentThread != null && currentThread.isAlive()) { + currentThread.setPriority(Thread.MIN_PRIORITY); + stalledThreads.add(currentThread); + updateStalledThreads(); + LOG.info("Number of stalled threads: " + stalledThreads.size()); + } else { + LOG.info("Number of stalled threads: " + 0); + } + + if (threadGroup != null) { + PermissionStatistics.getInstance().countThreads(threadGroup.activeCount()); + } + threadGroup = new ThreadGroup(BOTSING_TEST_EXECUTION_THREAD_GROUP); + currentThread = new Thread(threadGroup, r); + currentThread.setName(BOTSING_TEST_EXECUTION_THREAD + "_" + threadCounter); + threadCounter++; + currentThread.setContextClassLoader(BotsingTestGenerationContext.getInstance().getClassLoaderForSUT()); + ExecutionTracer.setThread(currentThread); + return currentThread; + + } + + private void updateStalledThreads(){ + Iterator iterator = stalledThreads.iterator(); + while (iterator.hasNext()) { + Thread currentThread = iterator.next(); + if(!currentThread.isAlive()){ + iterator.remove(); + } + } + } + + + public void newObservers() { + observers = new LinkedHashSet<>(); + } +} diff --git a/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/testusage/TestUsagePool.java b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/testusage/TestUsagePool.java new file mode 100644 index 0000000..513bbe2 --- /dev/null +++ b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/testusage/TestUsagePool.java @@ -0,0 +1,23 @@ +package eu.stamp.botsing.model.generation.testusage; + + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; + +public class TestUsagePool { + private static final Logger LOG = LoggerFactory.getLogger(TestUsagePool.class); + protected Map> pool = new HashMap>(); + + public void addTest(String usedClass,String testName){ + LOG.info("Adding test {} to class {}",testName,usedClass); + if(!this.pool.containsKey(usedClass)){ + this.pool.put(usedClass,new ArrayList<>()); + } + + if(!this.pool.get(usedClass).contains(testName)){ + this.pool.get(usedClass).add(testName); + } + } +} diff --git a/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/testusage/TestUsagePoolManager.java b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/testusage/TestUsagePoolManager.java new file mode 100644 index 0000000..2b66da9 --- /dev/null +++ b/botsing-model-generation/src/main/java/eu/stamp/botsing/model/generation/testusage/TestUsagePoolManager.java @@ -0,0 +1,41 @@ +package eu.stamp.botsing.model.generation.testusage; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.PrintWriter; +import java.nio.file.Paths; + +public class TestUsagePoolManager extends TestUsagePool { + private static final Logger LOG = LoggerFactory.getLogger(TestUsagePoolManager.class); + private static TestUsagePoolManager instance = null; + private TestUsagePoolManager(){} + + public static TestUsagePoolManager getInstance(){ + if(instance==null){ + instance = new TestUsagePoolManager(); + } + return instance; + } + + + + public void savingTestsUsages(String outputPath) { + Gson gson = new GsonBuilder().create(); + String json = gson.toJson(this.pool); + File outDirectory = new File(outputPath); + if (!outDirectory.exists()) { + outDirectory.mkdirs(); + } + try (PrintWriter out = new PrintWriter(Paths.get(outputPath,"tests.xml").toString())) { + out.println(json); + LOG.info("The saved test usage is: {}",json); + } catch (FileNotFoundException e) { + LOG.error("The output directory for carved tests is not valid."); + } + } +} diff --git a/botsing-model-generation/src/main/resources/logback.xml b/botsing-model-generation/src/main/resources/logback.xml new file mode 100644 index 0000000..d9fb870 --- /dev/null +++ b/botsing-model-generation/src/main/resources/logback.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + \ No newline at end of file diff --git a/botsing-model-generation/src/test/java/eu/stamp/botsing/model/generation/ModelGenerationTest.java b/botsing-model-generation/src/test/java/eu/stamp/botsing/model/generation/ModelGenerationTest.java new file mode 100644 index 0000000..a808909 --- /dev/null +++ b/botsing-model-generation/src/test/java/eu/stamp/botsing/model/generation/ModelGenerationTest.java @@ -0,0 +1,65 @@ +package eu.stamp.botsing.model.generation; + +import ch.qos.logback.classic.Level; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.nio.file.Paths; + +import static org.hamcrest.Matchers.arrayWithSize; +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.io.FileMatchers.anExistingDirectory; +import static org.junit.Assert.assertThat; + +public class ModelGenerationTest { + + private static final Logger LOG = LoggerFactory.getLogger(ModelGenerationTest.class); + + @Rule + public TestRule watcher = new TestWatcher() { + @Override + protected void starting(Description description) { + LOG.info(String.format("Starting test: %s()...", + description.getMethodName())); + } + }; + + @Before + public void initialize() { + ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) org.slf4j.LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME); + root.setLevel(Level.INFO); + } + + @Test + public void testMain(){ + String user_dir = System.getProperty("user.dir"); // the current directory is the module botsing-model-generation + File file = new File(user_dir); + String base_dir = Paths.get(file.getParent(), "botsing-examples").toString(); + String bin_path = Paths.get(base_dir, "src","main","resources","sample_dep").toString(); + String classPrefix = "org.tudelft"; + File outputDir = Paths.get(user_dir, "target", "generated-models").toFile(); + //run Botsing + String[] prop = { + "-projectCP", + bin_path, + "-projectPrefix", + classPrefix, + "-outDir", + outputDir.getAbsolutePath() + }; + Main main = new Main(); + main.parseCommandLine(prop); + + // Check output directory + assertThat(outputDir, anExistingDirectory()); + assertThat(outputDir.list(), arrayWithSize(greaterThan(0))); + } + +} diff --git a/botsing-model-generation/src/test/java/eu/stamp/botsing/model/generation/callsequence/CallSequencePoolManagerTest.java b/botsing-model-generation/src/test/java/eu/stamp/botsing/model/generation/callsequence/CallSequencePoolManagerTest.java new file mode 100644 index 0000000..027a7e4 --- /dev/null +++ b/botsing-model-generation/src/test/java/eu/stamp/botsing/model/generation/callsequence/CallSequencePoolManagerTest.java @@ -0,0 +1,37 @@ +package eu.stamp.botsing.model.generation.callsequence; + +import org.evosuite.graphs.cfg.BytecodeInstruction; +import org.junit.Test; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.List; + +public class CallSequencePoolManagerTest { + + @Test + public void testSavePool(){ + + + BytecodeInstruction bc = Mockito.mock(BytecodeInstruction.class); + BytecodeInstruction fakeBC = Mockito.mock(BytecodeInstruction.class); + Mockito.when(bc.explain()).thenReturn("bc, mocked for unit testing"); + Mockito.when(bc.getCalledMethodsClass()).thenReturn("Class2"); + Mockito.when(bc.toString()).thenReturn("Class2"); + Mockito.when(bc.isConstructorInvocation()).thenReturn(false); + Mockito.when(bc.isCallToStaticMethod()).thenReturn(false); + Mockito.when(bc.getInstructionId()).thenReturn((int) Math.random()); + Mockito.when(bc.getMethodCallDescriptor()).thenReturn("(Ljava/lang/String;)V"); + Mockito.when(bc.getSourceOfMethodInvocationInstruction()).thenReturn(fakeBC); + Mockito.when(bc.getSourceOfMethodInvocationInstruction().getVariableName()).thenReturn("var0"); + + // mock callSequence + List callSequence = new ArrayList(); + callSequence.add(new MethodCall(bc)); + + + CallSequencesPoolManager.getInstance().addSequence("classA",callSequence); + + CallSequencesPoolManager.getInstance().savePool(""); + } +} diff --git a/botsing-model-generation/src/test/java/eu/stamp/botsing/model/generation/sourcecode/StaticAnalyserTest.java b/botsing-model-generation/src/test/java/eu/stamp/botsing/model/generation/sourcecode/StaticAnalyserTest.java new file mode 100644 index 0000000..6f07251 --- /dev/null +++ b/botsing-model-generation/src/test/java/eu/stamp/botsing/model/generation/sourcecode/StaticAnalyserTest.java @@ -0,0 +1,153 @@ +package eu.stamp.botsing.model.generation.sourcecode; + +import eu.stamp.botsing.commons.BotsingTestGenerationContext; +import eu.stamp.botsing.model.generation.analysis.sourcecode.StaticAnalyser; +import org.evosuite.graphs.GraphPool; +import org.evosuite.graphs.cfg.BytecodeInstruction; +import org.evosuite.graphs.cfg.RawControlFlowGraph; +import org.junit.Test; +import org.mockito.ArgumentMatchers; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.List; + +public class StaticAnalyserTest { + + @Test + public void testAnalyse_bc_is_constructor(){ + + // mock current bytecodeInstruction + BytecodeInstruction bc = Mockito.mock(BytecodeInstruction.class); + Mockito.when(bc.explain()).thenReturn("bc, mocked for unit testing"); + Mockito.when(bc.getCalledMethodsClass()).thenReturn("Class2"); + Mockito.when(bc.toString()).thenReturn("Class2"); + Mockito.when(bc.isConstructorInvocation()).thenReturn(true); + Mockito.when(bc.getInstructionId()).thenReturn((int) Math.random()); + Mockito.when(bc.getMethodCallDescriptor()).thenReturn("(Ljava/lang/String;)V"); + + + // mock next bytecodeInstruction + BytecodeInstruction next = Mockito.mock(BytecodeInstruction.class); + Mockito.when(next.getInstructionType()).thenReturn("ASTORE"); + Mockito.when(next.getVariableName()).thenReturn("var0"); + + // mock callSequence + List callSequence = new ArrayList(); + callSequence.add(bc); + + + + // mock raw cfg + RawControlFlowGraph rcfg = Mockito.mock(RawControlFlowGraph.class); + Mockito.when(rcfg.getClassName()).thenReturn("Class1"); + Mockito.when(rcfg.getMethodName()).thenReturn("methodA()"); + Mockito.when(rcfg.determineMethodCalls()).thenReturn(callSequence); + Mockito.when(rcfg.getInstruction(ArgumentMatchers.anyInt())).thenReturn(next); + + + + GraphPool.getInstance(BotsingTestGenerationContext.getInstance().getClassLoaderForSUT()).registerRawCFG(rcfg); + + + + List interestingClasses = new ArrayList<>(); + interestingClasses.add("Class1"); + StaticAnalyser staticAnalyser = new StaticAnalyser(); + staticAnalyser.analyse(interestingClasses); + } + + @Test + public void testAnalyse_bc_is_static_method_call(){ + + // mock current bytecodeInstruction + BytecodeInstruction bc = Mockito.mock(BytecodeInstruction.class); + Mockito.when(bc.explain()).thenReturn("bc, mocked for unit testing"); + Mockito.when(bc.getCalledMethodsClass()).thenReturn("Class2"); + Mockito.when(bc.toString()).thenReturn("Class2"); + Mockito.when(bc.isConstructorInvocation()).thenReturn(false); + Mockito.when(bc.isCallToStaticMethod()).thenReturn(true); + Mockito.when(bc.getInstructionId()).thenReturn((int) Math.random()); + Mockito.when(bc.getMethodCallDescriptor()).thenReturn("(Ljava/lang/String;)V"); + + + // mock next bytecodeInstruction + BytecodeInstruction next = Mockito.mock(BytecodeInstruction.class); + Mockito.when(next.getInstructionType()).thenReturn("ALOAD"); + Mockito.when(next.getVariableName()).thenReturn("var0"); + + // mock callSequence + List callSequence = new ArrayList(); + callSequence.add(bc); + + + + // mock raw cfg + RawControlFlowGraph rcfg = Mockito.mock(RawControlFlowGraph.class); + Mockito.when(rcfg.getClassName()).thenReturn("Class1"); + Mockito.when(rcfg.getMethodName()).thenReturn("methodA()"); + Mockito.when(rcfg.determineMethodCalls()).thenReturn(callSequence); + Mockito.when(rcfg.getInstruction(ArgumentMatchers.anyInt())).thenReturn(next); + + + + GraphPool.getInstance(BotsingTestGenerationContext.getInstance().getClassLoaderForSUT()).registerRawCFG(rcfg); + + + + List interestingClasses = new ArrayList<>(); + interestingClasses.add("Class1"); + StaticAnalyser staticAnalyser = new StaticAnalyser(); + staticAnalyser.analyse(interestingClasses); + } + + + @Test + public void testAnalyse_bc_is_regular_method_call(){ + testAnalyse_bc_is_constructor(); + // mock current bytecodeInstruction + BytecodeInstruction bc = Mockito.mock(BytecodeInstruction.class); + BytecodeInstruction fakeBC = Mockito.mock(BytecodeInstruction.class); + Mockito.when(bc.explain()).thenReturn("bc, mocked for unit testing"); + Mockito.when(bc.getCalledMethodsClass()).thenReturn("Class2"); + Mockito.when(bc.toString()).thenReturn("Class2"); + Mockito.when(bc.isConstructorInvocation()).thenReturn(false); + Mockito.when(bc.isCallToStaticMethod()).thenReturn(false); + Mockito.when(bc.getInstructionId()).thenReturn((int) Math.random()); + Mockito.when(bc.getMethodCallDescriptor()).thenReturn("(Ljava/lang/String;)V"); + Mockito.when(bc.getSourceOfMethodInvocationInstruction()).thenReturn(fakeBC); + Mockito.when(bc.getSourceOfMethodInvocationInstruction().getVariableName()).thenReturn("var0"); + + + + + // mock next bytecodeInstruction + BytecodeInstruction next = Mockito.mock(BytecodeInstruction.class); + Mockito.when(next.getInstructionType()).thenReturn("ALOAD"); + Mockito.when(next.getVariableName()).thenReturn("var0"); + + // mock callSequence + List callSequence = new ArrayList(); + callSequence.add(bc); + + + + // mock raw cfg + RawControlFlowGraph rcfg = Mockito.mock(RawControlFlowGraph.class); + Mockito.when(rcfg.getClassName()).thenReturn("Class1"); + Mockito.when(rcfg.getMethodName()).thenReturn("methodA()"); + Mockito.when(rcfg.determineMethodCalls()).thenReturn(callSequence); + Mockito.when(rcfg.getInstruction(ArgumentMatchers.anyInt())).thenReturn(next); + + + + GraphPool.getInstance(BotsingTestGenerationContext.getInstance().getClassLoaderForSUT()).registerRawCFG(rcfg); + + + + List interestingClasses = new ArrayList<>(); + interestingClasses.add("Class1"); + StaticAnalyser staticAnalyser = new StaticAnalyser(); + staticAnalyser.analyse(interestingClasses); + } +} diff --git a/botsing-model-generation/src/test/resources/logback-test.xml b/botsing-model-generation/src/test/resources/logback-test.xml new file mode 100644 index 0000000..d9fb870 --- /dev/null +++ b/botsing-model-generation/src/test/resources/logback-test.xml @@ -0,0 +1,14 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + \ No newline at end of file diff --git a/botsing-parsers/pom.xml b/botsing-parsers/pom.xml new file mode 100644 index 0000000..374ed1e --- /dev/null +++ b/botsing-parsers/pom.xml @@ -0,0 +1,80 @@ + + + + botsing + eu.stamp-project + 1.0.4-SNAPSHOT + + 4.0.0 + + botsing-parsers + + + + org.antlr + antlr4-runtime + ${antlr.version} + + + com.google.guava + guava + ${guava.version} + + + junit + junit + ${junit.version} + test + + + org.hamcrest + java-hamcrest + ${hamcrest.version} + test + + + org.mockito + mockito-core + ${mockito.version} + test + + + org.slf4j + slf4j-api + ${slf4j.version} + test + + + ch.qos.logback + logback-classic + ${logback-classic.version} + test + + + + + + + + org.antlr + antlr4-maven-plugin + ${antlr.version} + + + + antlr4 + + + + true + true + + + + + + + + \ No newline at end of file diff --git a/botsing-parsers/src/main/antlr4/eu/stamp/botsing/parsers/StackTracesLexer.g4 b/botsing-parsers/src/main/antlr4/eu/stamp/botsing/parsers/StackTracesLexer.g4 new file mode 100644 index 0000000..baaa177 --- /dev/null +++ b/botsing-parsers/src/main/antlr4/eu/stamp/botsing/parsers/StackTracesLexer.g4 @@ -0,0 +1,53 @@ +lexer grammar StackTracesLexer; + +// Symbols + +AT: 'at'; + +FILEEXTENSION: '.java'; + +CAUSED_BY: 'Caused by:'; + +MORE_: 'more'; + +NATIVE_METHOD: 'Native Method'; + +UNKNOWN_SOURCE: 'Unknown Source'; + +INIT: ''; + +DOLLAR: '$'; + +LPAR: '('; + +RPAR: ')'; + +DOT: '.'; + +ELLIPSIS: '...'; + +COLON: ':'; + +// Numbers + +NUMBER: (DIGIT)+; + +fragment DIGIT: '0' .. '9' ; + +// Identifiers + +ID: (UPPERCASE | LOWERCASE | UNDERSCORE | DIGIT)+; + +fragment LOWERCASE: 'a' .. 'z'; + +fragment UPPERCASE: 'A' .. 'Z'; + +UNDERSCORE: '_'; + +// Skip Whitespaces + +WS: (' ' | '\r' | '\t' | '\u000C' | '\n') -> skip; + +// Skip everything else + +OTHER : . -> skip ; \ No newline at end of file diff --git a/botsing-parsers/src/main/antlr4/eu/stamp/botsing/parsers/StackTracesParser.g4 b/botsing-parsers/src/main/antlr4/eu/stamp/botsing/parsers/StackTracesParser.g4 new file mode 100644 index 0000000..9122b8d --- /dev/null +++ b/botsing-parsers/src/main/antlr4/eu/stamp/botsing/parsers/StackTracesParser.g4 @@ -0,0 +1,48 @@ +parser grammar StackTracesParser; + +options { tokenVocab = StackTracesLexer; } + +stackTraces: ( content )* EOF; + +content: stackTrace # RootStackTrace + | . # MiscContent + ; + +stackTrace: messageLine atLine+ ellipsisLine? causedByLine?; + +atLine: AT qualifiedMethod LPAR classFile RPAR; + +ellipsisLine: ELLIPSIS NUMBER MORE_; + +causedByLine: CAUSED_BY stackTrace; + +messageLine: qualifiedClass (COLON message)?; + +qualifiedClass: packagePath? className innerClassName*; + +innerClassName: DOLLAR (NUMBER | className); + +classFile: fileLocation + | isNative + | isUnknown + ; + +fileLocation: fileName COLON NUMBER; + +isNative: NATIVE_METHOD; + +isUnknown: UNKNOWN_SOURCE; + +fileName: className FILEEXTENSION; + +qualifiedMethod: qualifiedClass DOT (methodName | constructor); + +constructor: INIT; + +methodName: ID; + +packagePath: (ID DOT) +; + +className: ID; + +message: .*?; diff --git a/botsing-parsers/src/main/java/eu/stamp/botsing/EllipsisFrame.java b/botsing-parsers/src/main/java/eu/stamp/botsing/EllipsisFrame.java new file mode 100644 index 0000000..cb3af77 --- /dev/null +++ b/botsing-parsers/src/main/java/eu/stamp/botsing/EllipsisFrame.java @@ -0,0 +1,91 @@ +package eu.stamp.botsing; + +import java.util.Objects; + +/** + * An Ellipsis frame with the given number of more frames ellipsed. The objects always have the ELLIPSIS_CLASS_NAME + * value as class name and ELLIPSIS_METHOD_NAME as method name. The fileName is always null and the frame is always + * unknown. + */ +public final class EllipsisFrame extends Frame { + + public static final String ELLIPSIS_CLASS_NAME = "ellipsis"; + public static final String ELLIPSIS_METHOD_NAME = "frame"; + + private int more; + + /** + * Builds a new ellipsis frame with ELLIPSIS_CLASS_NAME as class name and ELLIPSIS_METHOD_NAME as method name. The + * fileName is null and the frame is unknown. + * + * @param more The number of frames ellipsed. This value should be higher than 0. + */ + public EllipsisFrame(int more) { + super(ELLIPSIS_CLASS_NAME, ELLIPSIS_METHOD_NAME, null, IS_UNKNOWN); + this.more = more; + } + + @Override + public String getClassName() { + return ELLIPSIS_CLASS_NAME; + } + + @Override + public String getMethodName() { + return ELLIPSIS_METHOD_NAME; + } + + @Override + public String getFileName() { + return null; + } + + @Override + public int getLineNumber() { + return IS_UNKNOWN; + } + + @Override + public int hashCode() { + return Objects.hash(super.hashCode(), more); + } + + @Override + public boolean equals(Object o) { + if(this == o) { + return true; + } + if(o == null || getClass() != o.getClass()) { + return false; + } + if(!super.equals(o)) { + return false; + } + EllipsisFrame that = (EllipsisFrame) o; + return more == that.more; + } + + @Override + public String toString() { + return "... " + more + " more"; + } + + @Override + public boolean isNative() { + return false; + } + + @Override + public boolean isUnknown() { + return true; + } + + /** + * Returns the number of frames ellipsed. + * + * @return The number of ellipsed frames. + */ + public int howManyMore() { + return this.more; + } +} diff --git a/botsing-parsers/src/main/java/eu/stamp/botsing/Frame.java b/botsing-parsers/src/main/java/eu/stamp/botsing/Frame.java new file mode 100644 index 0000000..8359000 --- /dev/null +++ b/botsing-parsers/src/main/java/eu/stamp/botsing/Frame.java @@ -0,0 +1,174 @@ +package eu.stamp.botsing; + +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * This class represents a frame in a stack trace. A frame has a class and method names, and either a class file + line + * number, or 'Native Method' or 'Unknown Source' indication. + */ +public class Frame { + + public static final int IS_NATIVE = -1; + public static final int IS_UNKNOWN = -2; + + private String className; + private String methodName; + private String fileName; + private int lineNumber; + + /** + * Builds a new frame with the given class name and method name and that declared as unknown. + * + * @param className the name of the class in which the method is defined + * @param methodName the name of the method + */ + public Frame(String className, String methodName) { + this(className, methodName, null, IS_UNKNOWN); + } + + /** + * Builds a new Frame with the given class, method, file and line number. If lineNumber is IS_NATIVE or IS_UNKNOWN, + * then fileName must be null (and vice versa). + * + * @param className The name of the class (should not be null). + * @param methodName The name of the method (should not be null). + * @param fileName The name of the file or null if isNative or isUnknown. + * @param lineNumber The line in the file or IS_NATIVE or IS_UNKNOWN. + * @throws IllegalArgumentException If line number is IS_NATIVE or IS_UNKNOWN and fileName is not null, or if file + * name is null and line number is not IS_NATIVE or IS_UNKNOWN. + */ + public Frame(String className, String methodName, String fileName, int lineNumber) throws IllegalArgumentException { + checkArgument(!(lineNumber == IS_UNKNOWN || lineNumber == IS_NATIVE) || (fileName == null), + "If line number " + "is IS_UNKNOWN or IS_NATIVE, then fileName must be null!"); + checkArgument(!(fileName == null) || (lineNumber == IS_UNKNOWN || lineNumber == IS_NATIVE), + "If fileName is " + "null, then line number should be IS_UNKNOWN or IS_NATIVE!"); + this.className = className; + this.methodName = methodName; + this.fileName = fileName; + this.lineNumber = lineNumber; + } + + /** + * Returns the name of the class. + * + * @return The name of the class. + */ + public String getClassName() { + return className; + } + + /** + * Sets the name of the class. + * + * @param className should not be null. + */ + public void setClassName(String className) throws IllegalArgumentException { + this.className = className; + } + + /** + * Returns the name of the method. + * + * @return The name of the method. + */ + public String getMethodName() { + return methodName; + } + + /** + * Sets the name of the method. + * + * @param methodName should not be null. + */ + public void setMethodName(String methodName) throws IllegalArgumentException { + this.methodName = methodName; + } + + /** + * Returns the name of the file where the method is defined or null if isNative or isUnknown. + * + * @return The name of the file where the method is defined or null if isNative or isUnknown. + */ + public String getFileName() { + return fileName; + } + + /** + * Returns the line number or IS_NATIVE if isNative or IS_UNKNOWN if isUnknown. + * + * @return The line number or IS_NATIVE if isNative or IS_UNKNOWN if isUnknown. + */ + public int getLineNumber() { + return lineNumber; + } + + /** + * Sets the location to the given line in the given file name. If lineNumber is IS_NATIVE or IS_UNKNOWN, then + * fileName must be null (and vice versa). + * + * @param fileName The name of the file. + * @param lineNumber The line in the file. + * @throws IllegalArgumentException If line number is IS_NATIVE or IS_UNKNOWN and fileName is not null, or if file + * name is null and line number is not IS_NATIVE or IS_UNKNOWN + */ + public void setLocation(String fileName, int lineNumber) throws IllegalArgumentException { + checkArgument(!(lineNumber == IS_UNKNOWN || lineNumber == IS_NATIVE) || (fileName == null), + "If line number " + "is IS_UNKNOWN or IS_NATIVE, then fileName must be null!"); + checkArgument(!(fileName == null) || (lineNumber == IS_UNKNOWN || lineNumber == IS_NATIVE), + "If fileName is " + "null, then line number should be IS_UNKNOWN or IS_NATIVE!"); + this.fileName = fileName; + this.lineNumber = lineNumber; + } + + @Override + public int hashCode() { + return Objects.hash(className, methodName, fileName, lineNumber); + } + + @Override + public boolean equals(Object o) { + if(this == o) { + return true; + } + if(o == null || getClass() != o.getClass()) { + return false; + } + Frame frame = (Frame) o; + return lineNumber == frame.lineNumber && Objects.equals(className, frame.className) && Objects.equals(methodName, frame.methodName) && Objects.equals(fileName, frame.fileName); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder = builder.append("\tat ").append(className).append('.').append(methodName).append('('); + if(isNative()) { + builder = builder.append("Native Method"); + } else if(isUnknown()) { + builder = builder.append("Unknown Source"); + } else { + builder = builder.append(fileName).append(':').append(lineNumber); + } + builder = builder.append(')'); + return builder.toString(); + } + + /** + * Indicates if the methods is declared native in the stack trace. + * + * @return True if the methods is declared native in the stack trace. + */ + public boolean isNative() { + return this.lineNumber == IS_NATIVE; + } + + /** + * Indicates if the methods is declared unknown in the stack trace. + * + * @return True if the methods is declared unknown in the stack trace. + */ + public boolean isUnknown() { + return this.lineNumber == IS_UNKNOWN; + } +} diff --git a/botsing-parsers/src/main/java/eu/stamp/botsing/StackTrace.java b/botsing-parsers/src/main/java/eu/stamp/botsing/StackTrace.java new file mode 100644 index 0000000..0986507 --- /dev/null +++ b/botsing-parsers/src/main/java/eu/stamp/botsing/StackTrace.java @@ -0,0 +1,191 @@ +package eu.stamp.botsing; + + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkArgument; + +/** + * This class represents a Java stack trace. Each stack trace has an exception type, an error message and a list of + * frames. Each stack trace may optinally have a cause (i.e., another stack trace with the encapsulated exception). The + * last frame of a stack trace is either a regular frame or an ellipsis indicating that the remainder of the stack trace + * has been stripped. + */ +public class StackTrace implements Iterable { + + private String exceptionClass; + private String errorMessage; + private List frames; + private StackTrace cause = null; + + /** + * Creates a new empty stack trace. + */ + public StackTrace() { + this(null, null); + } + + /** + * Creates a new stack trace with the given exception class and error message. + * + * @param exceptionClass The exception class name. + * @param errorMessage The error message for this stack trace. + */ + public StackTrace(String exceptionClass, String errorMessage) { + this(exceptionClass, errorMessage, null); + } + + /** + * Creates a new stack trace that has the given cause and with the given exception class and error message. + * + * @param exceptionClass The exception class name. + * @param errorMessage The error message for this stack trace. May be null. + * @param cause The cause of this stack trace. May be null. + */ + public StackTrace(String exceptionClass, String errorMessage, StackTrace cause) { + this.exceptionClass = exceptionClass; + this.errorMessage = errorMessage; + this.frames = new ArrayList<>(); + this.cause = cause; + } + + /** + * Returns the exception class. + * + * @return The exception class + */ + public String getExceptionClass() { + return exceptionClass; + } + + /** + * Sets the exception class. Exceptions class should not be null. + * + * @param exceptionClass The new exception class. + */ + public void setExceptionClass(String exceptionClass) { + this.exceptionClass = exceptionClass; + } + + /** + * Returns the error message (if any). + * + * @return The error message. + */ + public String getErrorMessage() { + return errorMessage; + } + + /** + * Sets the error message. + * + * @param errorMessage The new error message. + */ + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + /** + * Returns the cause of this stack trace (if any). + * + * @return The cause of this stack trace. + */ + public StackTrace getCause() { + return cause; + } + + /** + * Sets the cause of this stack trace. + * + * @param stackTrace The new cause of this stack trace. There should be no cycle in the stack trace causes. + */ + public void setCause(StackTrace stackTrace) { + this.cause = stackTrace; + } + + @Override + public Iterator iterator() { + return frames.iterator(); + } + + /** + * Removes the frame at the given level. The level should be between 1 and highestFrameLevel (incl.). + * + * @param level The level of the frame to remove. + * @return The removed frame. + */ + public Frame removeFrame(int level) { + return frames.remove(level - 1); + } + + /** + * Append the given frame to the end of the stack trace. If this frame is an EllipsisFrame, no more frames can be + * added afterwards. + * + * @param frame The frame to add. Should not be null. + * @throws IllegalArgumentException If the last frame of the stack trace is an EllipsisFrame or if the given frame + * is null. + */ + public void addFrame(Frame frame) throws IllegalArgumentException { + checkArgument(!(highestFrameLevel() > 0) || !(getFrame(highestFrameLevel()) instanceof EllipsisFrame), + "Highest frame is an ellipse, no more frames can be added to this stack trace!"); + checkArgument(frame != null, "Given frame may not be null!"); + frames.add(frame); + } + + /** + * Returns the highest frame level. Frames are indexed from 1 to maxLevel. + * + * @return The highest frame level. + */ + public int highestFrameLevel() { + return frames.size(); + } + + /** + * Returns the frame at the given level. The level should be between 1 and highestFrameLevel (incl.). + * + * @param level The level of the frame. + * @return The frame at the given level. + */ + public Frame getFrame(int level) throws IllegalArgumentException { + checkArgument(level > 0 && level <= highestFrameLevel(), "Level should be between 1 and %s (incl.)!", + frames.size()); + return frames.get(level - 1); + } + + @Override + public int hashCode() { + return Objects.hash(exceptionClass, errorMessage, frames, cause); + } + + @Override + public boolean equals(Object o) { + if(this == o) { + return true; + } + if(o == null || getClass() != o.getClass()) { + return false; + } + StackTrace stackTrace = (StackTrace) o; + return Objects.equals(exceptionClass, stackTrace.exceptionClass) && Objects.equals(errorMessage, + stackTrace.errorMessage) && Objects.equals(frames, stackTrace.frames) && Objects.equals(cause, + stackTrace.cause); + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + builder = builder.append(exceptionClass).append(':').append(errorMessage).append('\n'); + for(Frame f : this) { + builder = builder.append(f.toString()).append('\n'); + } + if(cause != null) { + builder = builder.append("Caused by: ").append(cause.toString()); + } + return builder.toString(); + } +} \ No newline at end of file diff --git a/botsing-parsers/src/main/java/eu/stamp/botsing/parsers/StackTracesCollectorListener.java b/botsing-parsers/src/main/java/eu/stamp/botsing/parsers/StackTracesCollectorListener.java new file mode 100644 index 0000000..f80cecb --- /dev/null +++ b/botsing-parsers/src/main/java/eu/stamp/botsing/parsers/StackTracesCollectorListener.java @@ -0,0 +1,125 @@ +package eu.stamp.botsing.parsers; + +import eu.stamp.botsing.EllipsisFrame; +import eu.stamp.botsing.Frame; +import eu.stamp.botsing.StackTrace; +import org.antlr.v4.runtime.misc.Interval; + +import java.util.ArrayList; +import java.util.List; + +/** + * Collects the stack traces in a given abstract syntax tree. To be used with a ParseTreeWalker object. + * + * @see org.antlr.v4.runtime.tree.ParseTreeWalker + */ +public class StackTracesCollectorListener extends StackTracesParserBaseListener { + + private List stackTraces; + + private StackTrace current = null; + + private String message = null; + private String className = null; + private String methodName = null; + private String fileName = null; + private int lineNumber = 0; + + /** + * Creates a new stack trace collector. + */ + public StackTracesCollectorListener() { + stackTraces = new ArrayList<>(); + } + + @Override + public void enterStackTraces(StackTracesParser.StackTracesContext ctx) { + stackTraces.clear(); + } + + @Override + public void enterRootStackTrace(StackTracesParser.RootStackTraceContext ctx) { + current = new StackTrace(); + stackTraces.add(current); + } + + @Override + public void exitAtLine(StackTracesParser.AtLineContext ctx) { + String className = this.className; + String methodName = this.methodName; + Frame frame = new Frame(className, methodName); + frame.setLocation(fileName, lineNumber); + current.addFrame(frame); + } + + @Override + public void exitEllipsisLine(StackTracesParser.EllipsisLineContext ctx) { + int more = Integer.parseInt(ctx.NUMBER().getText()); + Frame frame = new EllipsisFrame(more); + current.addFrame(frame); + } + + @Override + public void enterCausedByLine(StackTracesParser.CausedByLineContext ctx) { + StackTrace cause = new StackTrace(); + current.setCause(cause); + current = cause; + } + + @Override + public void exitMessageLine(StackTracesParser.MessageLineContext ctx) { + current.setExceptionClass(ctx.qualifiedClass().getText()); + current.setErrorMessage(message); + message = null; + } + + @Override + public void enterQualifiedClass(StackTracesParser.QualifiedClassContext ctx) { + className = ctx.getText(); + } + + @Override + public void enterFileLocation(StackTracesParser.FileLocationContext ctx) { + fileName = ctx.fileName().getText(); + lineNumber = Integer.parseInt(ctx.NUMBER().getText()); + } + + @Override + public void enterIsNative(StackTracesParser.IsNativeContext ctx) { + fileName = null; + lineNumber = Frame.IS_NATIVE; + } + + @Override + public void enterIsUnknown(StackTracesParser.IsUnknownContext ctx) { + fileName = null; + lineNumber = Frame.IS_UNKNOWN; + } + + @Override + public void enterConstructor(StackTracesParser.ConstructorContext ctx) { + methodName = ctx.getText(); + } + + @Override + public void enterMethodName(StackTracesParser.MethodNameContext ctx) { + methodName = ctx.getText(); + } + + @Override + public void enterMessage(StackTracesParser.MessageContext ctx) { + int start = ctx.start.getStartIndex(); + int stop = ctx.stop.getStopIndex(); + Interval interval = new Interval(start, stop); + message = ctx.start.getInputStream().getText(interval); + } + + /** + * Returns the collected stack traces. The list is cleared each time the collector is used by a walker. + * + * @return The collected stack traces. + */ + public List getStackTraces() { + return stackTraces; + } +} diff --git a/botsing-parsers/src/main/java/eu/stamp/botsing/parsers/StackTracesParsing.java b/botsing-parsers/src/main/java/eu/stamp/botsing/parsers/StackTracesParsing.java new file mode 100644 index 0000000..417d5e9 --- /dev/null +++ b/botsing-parsers/src/main/java/eu/stamp/botsing/parsers/StackTracesParsing.java @@ -0,0 +1,38 @@ +package eu.stamp.botsing.parsers; + +import eu.stamp.botsing.StackTrace; +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.tree.ParseTreeWalker; + +import java.util.List; + +/** + * Utility class to parse stack traces. + */ +public class StackTracesParsing { + + /** + * Parses the given input and returns a list of stack traces found in that input. + * @param input The input to parse. + * @return A list with the stack traces parsed from the given input. + */ + public static List parseStackTraces(String input){ + CharStream text = CharStreams.fromString(input); + StackTracesLexer lexer = new StackTracesLexer(text); + CommonTokenStream tokens = new CommonTokenStream(lexer); + StackTracesParser parser = new StackTracesParser(tokens); + StackTracesParser.StackTracesContext tree = parser.stackTraces(); + // Walk the three to build the stack traces + StackTracesCollectorListener listener = new StackTracesCollectorListener(); + ParseTreeWalker walker = new ParseTreeWalker(); + walker.walk(listener, tree); + // Return the built list + return listener.getStackTraces(); + } + + + + +} diff --git a/botsing-parsers/src/test/java/eu/stamp/botsing/EllipsisFrameTest.java b/botsing-parsers/src/test/java/eu/stamp/botsing/EllipsisFrameTest.java new file mode 100644 index 0000000..ce3809c --- /dev/null +++ b/botsing-parsers/src/test/java/eu/stamp/botsing/EllipsisFrameTest.java @@ -0,0 +1,96 @@ +package eu.stamp.botsing; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.Assert.*; +import static org.hamcrest.Matchers.*; + + +public class EllipsisFrameTest { + + private static final Logger LOG = LoggerFactory.getLogger(EllipsisFrameTest.class); + + @Rule + public TestRule watcher = new TestWatcher() { + @Override + protected void starting(Description description) { + LOG.info(String.format("Starting test: %s()...", + description.getMethodName())); + } + }; + + @Test + public void testNew() { + int more = 42; + EllipsisFrame frame = new EllipsisFrame(more); + assertThat(frame.getClassName(), equalTo(EllipsisFrame.ELLIPSIS_CLASS_NAME)); + assertThat(frame.getMethodName(), equalTo(EllipsisFrame.ELLIPSIS_METHOD_NAME)); + assertThat(frame.getFileName(), nullValue()); + assertThat(frame.getLineNumber(), equalTo(Frame.IS_UNKNOWN)); + assertThat(frame.isNative(), equalTo(false)); + assertThat(frame.isUnknown(), equalTo(true)); + assertThat(frame.howManyMore(), equalTo(more)); + } + + @Test + public void testImmutable() { + int more = 42; + EllipsisFrame frame = new EllipsisFrame(more); + assertThat(frame.getClassName(), is(EllipsisFrame.ELLIPSIS_CLASS_NAME)); + assertThat(frame.getMethodName(), is(EllipsisFrame.ELLIPSIS_METHOD_NAME)); + assertThat(frame.getFileName(), nullValue()); + assertThat(frame.getLineNumber(), is(Frame.IS_UNKNOWN)); + assertThat(frame.isNative(), is(false)); + assertThat(frame.isUnknown(), is(true)); + assertThat(frame.howManyMore(), equalTo(more)); + + + frame.setClassName("blabla"); + assertThat(frame.getClassName(), is(EllipsisFrame.ELLIPSIS_CLASS_NAME)); + frame.setMethodName("bla"); + assertThat(frame.getMethodName(), is(EllipsisFrame.ELLIPSIS_METHOD_NAME)); + frame.setLocation("file.java", 24); + assertThat(frame.getFileName(), nullValue()); + assertThat(frame.getLineNumber(), is(Frame.IS_UNKNOWN)); + assertThat(frame.isNative(), is(false)); + assertThat(frame.isUnknown(), is(true)); + assertThat(frame.howManyMore(), equalTo(more)); + } + + @Test + public void testEquals() { + int more = 42; + EllipsisFrame frame = new EllipsisFrame(more); + assertThat(frame.equals(frame), is(true)); + assertThat(frame.equals(new EllipsisFrame(more)), is(true)); + assertThat(frame.equals(new EllipsisFrame(24)), is(false)); + assertThat(frame.equals(new Frame("test", "methodtest", "file.java", 24)), is(false)); + + } + + @Test + public void testHashCode() { + int more = 42; + EllipsisFrame frame = new EllipsisFrame(more); + assertThat("Hash codes generated on the same objects should be equal!", + frame.hashCode(), equalTo(frame.hashCode())); + assertThat("Hash codes generated on equal objects should be equal!", + frame.hashCode(), equalTo(new EllipsisFrame(more).hashCode())); + assertThat("Hash codes generated on non equal objects should be different!", + frame.hashCode(), not(new EllipsisFrame(24).hashCode())); + } + + @Test + public void testToString() { + int more = 42; + EllipsisFrame frame = new EllipsisFrame(more); + assertThat(frame.toString(), not(emptyString())); + } + +} \ No newline at end of file diff --git a/botsing-parsers/src/test/java/eu/stamp/botsing/FrameTest.java b/botsing-parsers/src/test/java/eu/stamp/botsing/FrameTest.java new file mode 100644 index 0000000..22e1e01 --- /dev/null +++ b/botsing-parsers/src/test/java/eu/stamp/botsing/FrameTest.java @@ -0,0 +1,203 @@ +package eu.stamp.botsing; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.Assert.*; +import static org.hamcrest.Matchers.*; + +public class FrameTest { + + private static final Logger LOG = LoggerFactory.getLogger(FrameTest.class); + + @Rule + public TestRule watcher = new TestWatcher() { + @Override + protected void starting(Description description) { + LOG.info(String.format("Starting test: %s()...", + description.getMethodName())); + } + }; + + @Test + public void testNewRegularFrame() { + String className = "clazz"; + String method = "method"; + String fileName = "file.java"; + int lineNumber = 42; + Frame frame = new Frame(className, method, fileName, lineNumber); + assertThat(frame.getClassName(), equalTo(className)); + assertThat(frame.getMethodName(), equalTo(method)); + assertThat(frame.getFileName(), equalTo(fileName)); + assertThat(frame.getLineNumber(), equalTo(lineNumber)); + assertThat(frame.isUnknown(), is(false)); + assertThat(frame.isNative(), is(false)); + } + + @Test + public void testNewUnknownFrame() { + String className = "clazz"; + String method = "method"; + Frame frame = new Frame(className, method); + assertThat(frame.getClassName(), equalTo(className)); + assertThat(frame.getMethodName(), equalTo(method)); + assertThat(frame.getFileName(), nullValue()); + assertThat(frame.getLineNumber(), equalTo(Frame.IS_UNKNOWN)); + assertThat(frame.isUnknown(), is(true)); + assertThat(frame.isNative(), is(false)); + } + + @Test + public void testNewNativeFrame() { + String className = "clazz"; + String method = "method"; + String fileName = null; + int lineNumber = Frame.IS_NATIVE; + Frame frame = new Frame(className, method, fileName, lineNumber); + assertThat(frame.getClassName(), equalTo(className)); + assertThat(frame.getMethodName(), equalTo(method)); + assertThat(frame.getFileName(), nullValue()); + assertThat(frame.getLineNumber(), equalTo(lineNumber)); + assertThat(frame.isUnknown(), is(false)); + assertThat(frame.isNative(), is(true)); + } + + + @Test + public void testSetLocation() { + String className = "clazz"; + String method = "method"; + String fileName = "file.java"; + int lineNumber = 42; + Frame frame = new Frame(className, method, fileName, lineNumber); + assertThat(frame.getClassName(), equalTo(className)); + assertThat(frame.getMethodName(), equalTo(method)); + assertThat(frame.getFileName(), equalTo(fileName)); + assertThat(frame.getLineNumber(), equalTo(lineNumber)); + assertThat(frame.isUnknown(), is(false)); + assertThat(frame.isNative(), is(false)); + + frame.setLocation(null, Frame.IS_UNKNOWN); + assertThat(frame.getClassName(), equalTo(className)); + assertThat(frame.getMethodName(), equalTo(method)); + assertThat(frame.getFileName(), nullValue()); + assertThat(frame.getLineNumber(), equalTo(Frame.IS_UNKNOWN)); + assertThat(frame.isUnknown(), is(true)); + assertThat(frame.isNative(), is(false)); + + frame.setLocation(null, Frame.IS_NATIVE); + assertThat(frame.getClassName(), equalTo(className)); + assertThat(frame.getMethodName(), equalTo(method)); + assertThat(frame.getFileName(), nullValue()); + assertThat(frame.getLineNumber(), equalTo(Frame.IS_NATIVE)); + assertThat(frame.isUnknown(), is(false)); + assertThat(frame.isNative(), is(true)); + } + + @Test + public void testSetClassName() { + String className = "clazz"; + String method = "method"; + String fileName = "file.java"; + int lineNumber = 42; + Frame frame = new Frame(className, method, fileName, lineNumber); + assertThat(frame.getClassName(), equalTo(className)); + assertThat(frame.getMethodName(), equalTo(method)); + assertThat(frame.getFileName(), equalTo(fileName)); + assertThat(frame.getLineNumber(), equalTo(lineNumber)); + assertThat(frame.isUnknown(), is(false)); + assertThat(frame.isNative(), is(false)); + + className = "clazz2"; + frame.setClassName(className); + assertThat(frame.getClassName(), equalTo(className)); + assertThat(frame.getMethodName(), equalTo(method)); + assertThat(frame.getFileName(), equalTo(fileName)); + assertThat(frame.getLineNumber(), equalTo(lineNumber)); + assertThat(frame.isUnknown(), is(false)); + assertThat(frame.isNative(), is(false)); + } + + @Test + public void testSetMethodName() { + String className = "clazz"; + String method = "method"; + String fileName = "file.java"; + int lineNumber = 42; + Frame frame = new Frame(className, method, fileName, lineNumber); + assertThat(frame.getClassName(), equalTo(className)); + assertThat(frame.getMethodName(), equalTo(method)); + assertThat(frame.getFileName(), equalTo(fileName)); + assertThat(frame.getLineNumber(), equalTo(lineNumber)); + assertThat(frame.isUnknown(), is(false)); + assertThat(frame.isNative(), is(false)); + + method = "method2"; + frame.setMethodName(method); + assertThat(frame.getClassName(), equalTo(className)); + assertThat(frame.getMethodName(), equalTo(method)); + assertThat(frame.getFileName(), equalTo(fileName)); + assertThat(frame.getLineNumber(), equalTo(lineNumber)); + assertThat(frame.isUnknown(), is(false)); + assertThat(frame.isNative(), is(false)); + } + + @Test + public void testEquals() { + String className = "clazz"; + String method = "method"; + String fileName = "file.java"; + int lineNumber = 42; + Frame frame = new Frame(className, method, fileName, lineNumber); + + assertThat(frame.equals(frame), is(true)); + assertThat(frame.equals(new Frame(className, method, fileName, lineNumber)), is(true)); + + assertThat(frame.equals(new Frame("other", method, fileName, lineNumber)), is(false)); + assertThat(frame.equals(new Frame(className, "other", fileName, lineNumber)), is(false)); + assertThat(frame.equals(new Frame(className, method, "other", lineNumber)), is(false)); + assertThat(frame.equals(new Frame(className, method, fileName, 24)), is(false)); + + assertThat(frame.equals(new Frame(className, method)), is(false)); + } + + + @Test + public void testHashCode() { + String className = "clazz"; + String method = "method"; + String fileName = "file.java"; + int lineNumber = 42; + Frame frame = new Frame(className, method, fileName, lineNumber); + + assertThat("Hash codes generated on the same objects should be equal!", + frame.hashCode(), equalTo(frame.hashCode())); + assertThat("Hash codes generated on equal objects should be equal!", + frame.hashCode(), equalTo(new Frame(className, method, fileName, lineNumber).hashCode())); + + assertThat("Hash codes generated on non equal objects should be different!", + frame.hashCode(), not(new Frame("other", method, fileName, lineNumber).hashCode())); + assertThat("Hash codes generated on non equal objects should be different!", + frame.hashCode(), not(new Frame(className, "other", fileName, lineNumber).hashCode())); + assertThat("Hash codes generated on non equal objects should be different!", + frame.hashCode(), not(new Frame(className, method, "other", lineNumber).hashCode())); + assertThat("Hash codes generated on non equal objects should be different!", + frame.hashCode(), not(new Frame(className, method, fileName, 24).hashCode())); + } + + @Test + public void testToString() { + String className = "clazz"; + String method = "method"; + String fileName = "file.java"; + int lineNumber = 42; + Frame frame = new Frame(className, method, fileName, lineNumber); + assertThat(frame.toString(), not(emptyString())); + } + +} \ No newline at end of file diff --git a/botsing-parsers/src/test/java/eu/stamp/botsing/StackTraceTest.java b/botsing-parsers/src/test/java/eu/stamp/botsing/StackTraceTest.java new file mode 100644 index 0000000..3afacc9 --- /dev/null +++ b/botsing-parsers/src/test/java/eu/stamp/botsing/StackTraceTest.java @@ -0,0 +1,186 @@ +package eu.stamp.botsing; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.Assert.*; +import static org.hamcrest.Matchers.*; + +public class StackTraceTest { + + private static final Logger LOG = LoggerFactory.getLogger(StackTraceTest.class); + + @Rule + public TestRule watcher = new TestWatcher() { + @Override + protected void starting(Description description) { + LOG.info(String.format("Starting test: %s()...", + description.getMethodName())); + } + }; + + @Test + public void testNewSimpleStackTrace() { + String exception = "NullPointerOfCourse"; + String message = "because why not!"; + StackTrace st = new StackTrace(exception, message); + + assertThat(st.getExceptionClass(), equalTo(exception)); + assertThat(st.getErrorMessage(), equalTo(message)); + assertThat(st.getCause(), nullValue()); + assertThat(st.highestFrameLevel(), equalTo(0)); + assertThat(st, iterableWithSize(0)); + + st.addFrame(new Frame("clazz", "method")); + st.addFrame(new Frame("clazz2", "method2")); + } + + @Test + public void testAddFrame() { + String exception = "NullPointerOfCourse"; + String message = "because why not!"; + StackTrace st = new StackTrace(exception, message); + st.addFrame(new Frame("clazz", "method")); + st.addFrame(new Frame("clazz2", "method2")); + + assertThat(st.getExceptionClass(), equalTo(exception)); + assertThat(st.getErrorMessage(), equalTo(message)); + assertThat(st.getCause(), nullValue()); + assertThat(st.highestFrameLevel(), equalTo(2)); + assertThat(st, iterableWithSize(2)); + } + + @Test + public void testRemoveFrame() { + String exception = "NullPointerOfCourse"; + String message = "because why not!"; + StackTrace st = new StackTrace(exception, message); + st.addFrame(new Frame("clazz", "method")); + st.addFrame(new Frame("clazz2", "method2")); + + assertThat(st.highestFrameLevel(), equalTo(2)); + assertThat(st, iterableWithSize(2)); + + st.removeFrame(2); + assertThat(st.highestFrameLevel(), equalTo(1)); + assertThat(st, iterableWithSize(1)); + + st.removeFrame(1); + assertThat(st.highestFrameLevel(), equalTo(0)); + assertThat(st, iterableWithSize(0)); + } + + @Test(expected = IllegalArgumentException.class) + public void testAddEllipsisFrame() { + String exception = "NullPointerOfCourse"; + String message = "because why not!"; + StackTrace st = new StackTrace(exception, message); + st.addFrame(new Frame("clazz", "method")); + st.addFrame(new Frame("clazz2", "method2")); + + assertThat(st.highestFrameLevel(), equalTo(2)); + assertThat(st, iterableWithSize(2)); + + st.addFrame(new EllipsisFrame(34)); + + assertThat(st.highestFrameLevel(), equalTo(3)); + assertThat(st, iterableWithSize(3)); + + st.addFrame(new Frame("clazz3", "method3")); + } + + + @Test + public void testEquals() { + String exception = "NullPointerOfCourse"; + String message = "because why not!"; + StackTrace st = new StackTrace(exception, message); + StackTrace st2 = new StackTrace(); + st2.setExceptionClass(exception); + st2.setErrorMessage(message); + + assertThat(st.equals(st), is(true)); + assertThat(st.equals(st2), is(true)); + assertThat(st.equals(new StackTrace(exception, message, st2)), is(false)); + + st.addFrame(new Frame("clazz", "method")); + st.addFrame(new Frame("clazz2", "method2")); + assertThat(st.equals(st), is(true)); + assertThat(st.equals(st2), is(false)); + + st2.addFrame(new Frame("clazz", "method")); + st2.addFrame(new Frame("clazz2", "method2")); + assertThat(st.equals(st), is(true)); + assertThat(st.equals(st2), is(true)); + + st.setCause(new StackTrace("except", message)); + assertThat(st.equals(st), is(true)); + assertThat(st.equals(st2), is(false)); + + st2.setCause(new StackTrace("except", message)); + assertThat(st.equals(st), is(true)); + assertThat(st.equals(st2), is(true)); + } + + @Test + public void testHashCode() { + String exception = "NullPointerOfCourse"; + String message = "because why not!"; + StackTrace st = new StackTrace(exception, message); + StackTrace st2 = new StackTrace(exception, message); + + assertThat("Hash codes generated on the same objects should be equal!", + st.hashCode(), equalTo(st.hashCode())); + assertThat("Hash codes generated on equal objects should be equal!", + st.hashCode(), equalTo(st2.hashCode())); + assertThat("Hash codes generated on non equal objects should be different!", + st.hashCode(), not(new StackTrace(exception, message, st2).hashCode())); + + st.addFrame(new Frame("clazz", "method")); + st.addFrame(new Frame("clazz2", "method2")); + + assertThat("Hash codes generated on the same objects should be equal!", + st.hashCode(), equalTo(st.hashCode())); + assertThat("Hash codes generated on non equal objects should be different!", + st.hashCode(), not(st2.hashCode())); + + st2.addFrame(new Frame("clazz", "method")); + st2.addFrame(new Frame("clazz2", "method2")); + + assertThat("Hash codes generated on the same objects should be equal!", + st.hashCode(), equalTo(st.hashCode())); + assertThat("Hash codes generated on equal objects should be equal!", + st.hashCode(), equalTo(st2.hashCode())); + + st.setCause(new StackTrace("except", message)); + + assertThat("Hash codes generated on the same objects should be equal!", + st.hashCode(), equalTo(st.hashCode())); + assertThat("Hash codes generated on non equal objects should be different!", + st.hashCode(), not(st2.hashCode())); + + st2.setCause(new StackTrace("except", message)); + + assertThat("Hash codes generated on the same objects should be equal!", + st.hashCode(), equalTo(st.hashCode())); + assertThat("Hash codes generated on equal objects should be equal!", + st.hashCode(), equalTo(st2.hashCode())); + } + + @Test + public void testToString() { + String exception = "NullPointerOfCourse"; + String message = "because why not!"; + StackTrace st = new StackTrace(exception, message); + st.addFrame(new Frame("clazz", "method")); + st.setCause(new StackTrace("except", message)); + LOG.debug("Stack trace is: {}", st); + assertThat(st.toString(), not(emptyString())); + } + +} \ No newline at end of file diff --git a/botsing-parsers/src/test/java/eu/stamp/botsing/parsers/StackTracesParserTest.java b/botsing-parsers/src/test/java/eu/stamp/botsing/parsers/StackTracesParserTest.java new file mode 100644 index 0000000..62a4708 --- /dev/null +++ b/botsing-parsers/src/test/java/eu/stamp/botsing/parsers/StackTracesParserTest.java @@ -0,0 +1,126 @@ +package eu.stamp.botsing.parsers; + +import org.antlr.v4.runtime.CharStream; +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.Assert.*; +import static org.hamcrest.Matchers.*; + +public class StackTracesParserTest { + + private static final Logger LOG = LoggerFactory.getLogger(StackTracesParserTest.class); + + @Rule + public TestRule watcher = new TestWatcher() { + @Override + protected void starting(Description description) { + LOG.info(String.format("Starting test: %s()...", + description.getMethodName())); + } + }; + + private StackTracesParser setup(String inputString){ + CharStream text = CharStreams.fromString(inputString); + StackTracesLexer lexer = new StackTracesLexer(text); + CommonTokenStream tokens = new CommonTokenStream(lexer); + StackTracesParser parser = new StackTracesParser(tokens); + return parser; + } + + @Test + public void testSimpleStackTrace() { + StackTracesParser parser = setup("org.apache.commons.lang3.SerializationException: ClassNotFoundException " + + "while reading cloned object data\n" + + "\tat org.apache.commons.lang3.SerializationUtils.(SerializationUtils.java:99)\n" + + "\tat org.apache.commons.lang3.SerializationUtilsTest.testPrimitiveTypeClassSerialization" + + "(SerializationUtilsTest.java:373)\n" + + "\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n" + + "\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)"); + assertThat(parser.stackTraces().content(), hasSize(1)); + } + + @Test + public void testStackTraceWithInnerClasses() { + StackTracesParser parser = setup("org.apache.commons.lang3.SerializationException: ClassNotFoundException " + + "while reading cloned object data\n" + + "\tat org.apache.commons.lang3.SerializationUtils.clone(SerializationUtils.java:99)\n" + + "\tat junit.framework.TestResult$1.protect(TestResult.java:122)\n" + + "\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n" + + "\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n" + + "\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)"); + assertThat(parser.stackTraces().content(), hasSize(1)); + } + + @Test + public void testStackTraceWithNativeMethod() { + StackTracesParser parser = setup("org.apache.commons.lang3.SerializationException: ClassNotFoundException " + + "while reading cloned object data\n" + + "\tat org.apache.commons.lang3.SerializationUtils.clone(SerializationUtils.java:99)\n" + + "\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n" + + "\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n" + + "\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)"); + assertThat(parser.stackTraces().content(), hasSize(1)); + } + + @Test + public void testStackTraceWithUnknownSource() { + StackTracesParser parser = setup("org.apache.commons.lang3.SerializationException: ClassNotFoundException " + + "while reading cloned object data\n" + + "\tat org.apache.commons.lang3.SerializationUtils.clone(SerializationUtils.java:99)\n" + + "\tat sun.reflect.GeneratedMethodAccessor4.invoke(Unknown Source)\n" + + "\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n" + + "\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)"); + assertThat(parser.stackTraces().content(), hasSize(1)); + } + + @Test + public void testStackTraceWithCausedBy() { + StackTracesParser parser = setup("org.apache.commons.lang3.SerializationException: ClassNotFoundException " + + "while reading cloned object data\n" + + "\tat org.apache.commons.lang3.SerializationUtils.clone(SerializationUtils.java:99)\n" + + "\tat sun.reflect.GeneratedMethodAccessor4.invoke(Unknown Source)\n" + + "\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n" + + "\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n" + + "Caused by: java.lang.ClassNotFoundException: byte\n" + + "\tat org.apache.tools.ant.AntClassLoader.findClassInComponents(AntClassLoader.java:1365)\n" + + "\tat java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1613)\n" + + "\tat java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)\n" + + "\tat java.io.ObjectInputStream.readClass(ObjectInputStream.java:1484)"); + assertThat(parser.stackTraces().content(), hasSize(1)); + } + + @Test + public void testMultipleSimpleStackTraces() { + StackTracesParser parser = setup("org.apache.commons.lang3.SerializationException: ClassNotFoundException " + + "while reading cloned object data\n" + + "\tat org.apache.commons.lang3.SerializationUtils.clone(SerializationUtils.java:99)\n" + + "\tat sun.reflect.GeneratedMethodAccessor4.invoke(Unknown Source)\n" + + "\tat sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)\n" + + "\tat sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)\n" + + "java.lang.ClassNotFoundException: byte\n" + + "\tat org.apache.tools.ant.AntClassLoader.findClassInComponents(AntClassLoader.java:1365)\n" + + "\tat java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1613)\n" + + "\tat java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1518)\n" + + "\tat java.io.ObjectInputStream.readClass(ObjectInputStream.java:1484)"); + assertThat(parser.stackTraces().content(), hasSize(2)); + } + + @Test + public void testInvalidInput() { + StackTracesParser parser = setup("Lorem ipsum dolor sit amet"); + assertThat(parser.stackTraces().content(), hasSize(5)); + for(StackTracesParser.ContentContext ctx: parser.stackTraces().content()) { + assertThat(ctx, instanceOf(StackTracesParser.MiscContentContext.class)); + } + } + + +} diff --git a/botsing-parsers/src/test/java/eu/stamp/botsing/parsers/StackTracesParsingTest.java b/botsing-parsers/src/test/java/eu/stamp/botsing/parsers/StackTracesParsingTest.java new file mode 100644 index 0000000..a46f985 --- /dev/null +++ b/botsing-parsers/src/test/java/eu/stamp/botsing/parsers/StackTracesParsingTest.java @@ -0,0 +1,143 @@ +package eu.stamp.botsing.parsers; + +import eu.stamp.botsing.EllipsisFrame; +import eu.stamp.botsing.Frame; +import eu.stamp.botsing.StackTrace; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestRule; +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.assertThat; + +public class StackTracesParsingTest { + + private static final Logger LOG = LoggerFactory.getLogger(StackTracesParsingTest.class); + + @Rule + public TestRule watcher = new TestWatcher() { + @Override + protected void starting(Description description) { + LOG.info(String.format("Starting test: %s()...", + description.getMethodName())); + } + }; + + @Test + public void testSimpleStackTrace() { + String input = "org.apache.commons.lang3.SerializationException: ClassNotFoundException " + + "while reading cloned object data\n" + + "\tat org.apache.commons.lang3.SerializationUtils.clone(SerializationUtils.java:99)\n" + + "\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n" + + "\tat sun.reflect.GeneratedMethodAccessor4.invoke(Unknown Source)\n" + + "\t... 44 more"; + List stackTraces = StackTracesParsing.parseStackTraces(input); + assertThat(stackTraces, hasSize(1)); + + StackTrace st = stackTraces.get(0); + LOG.trace("Stack trace is {}", st); + assertThat(st.getExceptionClass(), equalTo("org.apache.commons.lang3.SerializationException")); + assertThat(st.getErrorMessage(), equalTo("ClassNotFoundException while reading cloned object data")); + assertThat(st.highestFrameLevel(), equalTo(4)); + + assertThat(st.getFrame(1), equalTo(new Frame("org.apache.commons.lang3.SerializationUtils", + "clone", "SerializationUtils.java", 99))); + assertThat(st.getFrame(2), equalTo(new Frame("sun.reflect.NativeMethodAccessorImpl", + "invoke0", null, Frame.IS_NATIVE))); + assertThat(st.getFrame(3), equalTo(new Frame("sun.reflect.GeneratedMethodAccessor4", + "invoke", null, Frame.IS_UNKNOWN))); + assertThat(st.getFrame(4), equalTo(new EllipsisFrame(44))); + } + + @Test + public void testSimpleStackTraceWithInit() { + String input = "org.apache.commons.lang3.SerializationException: ClassNotFoundException " + + "while reading cloned object data\n" + + "\tat org.apache.commons.lang3.SerializationUtils.(SerializationUtils.java:99)\n" + + "\tat sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)\n" + + "\tat sun.reflect.GeneratedMethodAccessor4.invoke(Unknown Source)\n" + + "\t... 44 more"; + List stackTraces = StackTracesParsing.parseStackTraces(input); + assertThat(stackTraces, hasSize(1)); + + StackTrace st = stackTraces.get(0); + LOG.trace("Stack trace is {}", st); + assertThat(st.getExceptionClass(), equalTo("org.apache.commons.lang3.SerializationException")); + assertThat(st.getErrorMessage(), equalTo("ClassNotFoundException while reading cloned object data")); + assertThat(st.highestFrameLevel(), equalTo(4)); + + assertThat(st.getFrame(1), equalTo(new Frame("org.apache.commons.lang3.SerializationUtils", + "", "SerializationUtils.java", 99))); + assertThat(st.getFrame(2), equalTo(new Frame("sun.reflect.NativeMethodAccessorImpl", + "invoke0", null, Frame.IS_NATIVE))); + assertThat(st.getFrame(3), equalTo(new Frame("sun.reflect.GeneratedMethodAccessor4", + "invoke", null, Frame.IS_UNKNOWN))); + assertThat(st.getFrame(4), equalTo(new EllipsisFrame(44))); + } + + + @Test + public void testStackTraceWithCause() { + String input = "org.apache.commons.lang3.SerializationException: ClassNotFoundException " + + "while reading cloned object data\n" + + "\tat org.apache.commons.lang3.SerializationUtils.(SerializationUtils.java:99)\n" + + "Caused by: java.lang.ClassNotFoundException: byte\n" + + "\tat org.apache.tools.ant.AntClassLoader.findClassInComponents(AntClassLoader.java:1365)\n" + + "\tat java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1613)\n"; + String cause = "java.lang.ClassNotFoundException: byte\n" + + "\tat org.apache.tools.ant.AntClassLoader.findClassInComponents(AntClassLoader.java:1365)\n" + + "\tat java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1613)\n"; + + List stackTraces = StackTracesParsing.parseStackTraces(input); + assertThat(stackTraces, hasSize(1)); + + StackTrace st = stackTraces.get(0); + LOG.trace("Stack trace is {}", st); + assertThat(st.getExceptionClass(), equalTo("org.apache.commons.lang3.SerializationException")); + assertThat(st.getErrorMessage(), equalTo("ClassNotFoundException while reading cloned object data")); + assertThat(st.highestFrameLevel(), equalTo(1)); + assertThat(st.getCause(), notNullValue()); + + StackTrace causeSt = StackTracesParsing.parseStackTraces(cause).get(0); + assertThat(st.getCause(), equalTo(causeSt)); + } + + @Test + public void testMultipleStackTraces() { + String input = "org.apache.commons.lang3.SerializationException: ClassNotFoundException " + + "while reading cloned object data\n" + + "\tat org.apache.commons.lang3.SerializationUtils.clone(SerializationUtils.java:99)\n" + + "java.lang.ClassNotFoundException: byte\n" + + "\tat org.apache.tools.ant.AntClassLoader.findClassInComponents(AntClassLoader.java:1365)\n" + + "\tat java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1613)\n"; + String first = "org.apache.commons.lang3.SerializationException: ClassNotFoundException " + + "while reading cloned object data\n" + + "\tat org.apache.commons.lang3.SerializationUtils.clone(SerializationUtils.java:99)\n"; + String second = "java.lang.ClassNotFoundException: byte\n" + + "\tat org.apache.tools.ant.AntClassLoader.findClassInComponents(AntClassLoader.java:1365)\n" + + "\tat java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1613)\n"; + + List stackTraces = StackTracesParsing.parseStackTraces(input); + assertThat(stackTraces, hasSize(2)); + + StackTrace firstSt = StackTracesParsing.parseStackTraces(first).get(0); + StackTrace secondSt = StackTracesParsing.parseStackTraces(second).get(0); + + assertThat(stackTraces.get(0), equalTo(firstSt)); + assertThat(stackTraces.get(1), equalTo(secondSt)); + } + + @Test + public void testInvalidInput() { + String input = "Lorem ipsum dolor sit amet"; + List stackTraces = StackTracesParsing.parseStackTraces(input); + assertThat(stackTraces, hasSize(0)); + } + +} diff --git a/botsing-parsers/src/test/resources/logback-test.xml b/botsing-parsers/src/test/resources/logback-test.xml new file mode 100644 index 0000000..a111473 --- /dev/null +++ b/botsing-parsers/src/test/resources/logback-test.xml @@ -0,0 +1,15 @@ + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + + + + \ No newline at end of file diff --git a/botsing-preprocessing/LICENSE.txt b/botsing-preprocessing/LICENSE.txt new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/botsing-preprocessing/LICENSE.txt @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/botsing-preprocessing/pom.xml b/botsing-preprocessing/pom.xml index 7b18eda..71b766c 100644 --- a/botsing-preprocessing/pom.xml +++ b/botsing-preprocessing/pom.xml @@ -1,10 +1,9 @@ - + botsing eu.stamp-project - 0.0.1-SNAPSHOT + 1.0.4-SNAPSHOT @@ -23,6 +22,12 @@ ${junit.version} test + + org.slf4j + slf4j-api + ${slf4j.version} + test + commons-cli @@ -31,19 +36,9 @@ - org.ow2.asm - asm - ${asm.version} - - - org.ow2.asm - asm-util - ${asm.version} - - - org.ow2.asm - asm-commons - ${asm.version} + org.slf4j + slf4j-api + 1.7.20 @@ -56,7 +51,7 @@ - eu.stamp_project.botsing.Main + eu.stamp.botsing.preprocessing.Main @@ -70,6 +65,54 @@ + + + org.apache.maven.plugins + maven-checkstyle-plugin + 2.17 + + + org.slf4j + jcl-over-slf4j + 1.7.5 + + + + + + + + org.eclipse.m2e + lifecycle-mapping + 1.0.0 + + + + + + + org.apache.maven.plugins + + + maven-checkstyle-plugin + + + [2.17,) + + + checkstyle + + + + + + + + + + + + \ No newline at end of file diff --git a/botsing-preprocessing/src/main/java/eu/stamp_project/botsing/ErrorMessage.java b/botsing-preprocessing/src/main/java/eu/stamp/botsing/ErrorMessage.java similarity index 58% rename from botsing-preprocessing/src/main/java/eu/stamp_project/botsing/ErrorMessage.java rename to botsing-preprocessing/src/main/java/eu/stamp/botsing/ErrorMessage.java index f6d2a46..0d74c95 100644 --- a/botsing-preprocessing/src/main/java/eu/stamp_project/botsing/ErrorMessage.java +++ b/botsing-preprocessing/src/main/java/eu/stamp/botsing/ErrorMessage.java @@ -1,8 +1,10 @@ -package eu.stamp_project.botsing; +package eu.stamp.botsing; import java.util.List; public class ErrorMessage implements STProcessor { + private final static String EXCEPTION_PREFIX = "Exception in thread"; + private static ErrorMessage instance = new ErrorMessage(); public static ErrorMessage get() { @@ -13,14 +15,14 @@ private ErrorMessage() { } @Override - public List preprocess(List lines) { + public List preprocess(List lines, String regexp) { if (lines.size() < 1) { return lines; } String head = lines.get(0); - // remove thread info - if (head.startsWith("Exception in thread")) { - head = head.replaceAll("Exception in thread \".*\" ", ""); + // remove thread info + if (head.startsWith(EXCEPTION_PREFIX)) { + head = head.replaceAll(EXCEPTION_PREFIX + " \".*\" ", ""); } if (head.contains(":")) { head = head.replaceAll("\\:.*", ""); diff --git a/botsing-preprocessing/src/main/java/eu/stamp/botsing/Main.java b/botsing-preprocessing/src/main/java/eu/stamp/botsing/Main.java new file mode 100644 index 0000000..f9d4d1b --- /dev/null +++ b/botsing-preprocessing/src/main/java/eu/stamp/botsing/Main.java @@ -0,0 +1,205 @@ +package eu.stamp.botsing; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.CommandLineParser; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; + +public class Main { + + static public Options options = initOptions(); + private static String HYPHENS = "---"; + + public static Options initOptions() { + Options opt = new Options(); + // define flags + Option flatten = new Option("f", "flatten", false, "use this option to flatten the stack trace"); + Option error = new Option("e", "error_message", false, "use this option to remove the error message"); + + // define parameters + Option crash_log = new Option("i", "crash_log", true, "path to the input stack trace"); + Option output_log = new Option("o", "output_log", true, "path to the output stack trace after processing"); + Option source = new Option("p", "package", true, "regular expression package pointing to the classes of the project"); + + // define required options + crash_log.setRequired(true); + output_log.setRequired(true); + + opt.addOption(crash_log); + opt.addOption(output_log); + opt.addOption(source); + opt.addOption(flatten); + opt.addOption(error); + + return opt; + } + + public static void main(String[] args) { + if (args.length == 0) { + printOptions(); + } + + CommandLineParser parser = new DefaultParser(); + try { + CommandLine cli = parser.parse(options, args); + boolean f = cli.hasOption('f'); + boolean e = cli.hasOption('e'); + + String input = cli.getOptionValue('i'); + String output = cli.getOptionValue('o'); + String regexp = cli.getOptionValue('p');// package + + if (!(f || e)) { + System.out.println("Wrong arguments. No '-f' or '-e' flag selected"); + printOptions(); + } + + if (f && regexp == null) { + System.out.println("Wrong arguments. For '-f' flag, it's necessary to set the regexp with '-p'"); + printOptions(); + } + + preprocess(f, e, input, output, regexp); + + } catch (ParseException e) { + System.out.println("Wrong arguments. " + e.getMessage()); + printOptions(); + } catch (FileNotFoundException e) { + System.out.println("Wrong arguments. " + e.getMessage()); + return; + } + } + + /** + * Performs the pre-processing on the stack trace based on the options + * + * @param flatten + * if true, a chained stack trace is flattened + * @param error + * if true, remove the error message + * @param input + * input file path + * @param output + * output file path + */ + public static void preprocess(boolean flatten, boolean error, String input, String output, String regexp) + throws FileNotFoundException { + File inputFile = new File(input); + if (!inputFile.exists()) { + throw new FileNotFoundException("Input file name '" + inputFile + "' does not exist!"); + } + File outFile = new File(output); + if (outFile.exists()) { + outFile.delete(); + } + List lines = fileToLines(inputFile); + + // pre-processing + if (flatten) { + lines = StackFlatten.get().preprocess(lines, regexp); + } + if (error) { + lines = ErrorMessage.get().preprocess(lines, null); + } + + // generate output log file + linesToFile(lines, outFile); + + System.out.println("End pre-processing"); + } + + /** + * reads a file and returns a list of lines for the content + * + * @param logPath + * path of the file + * @return List of lines + */ + static List fileToLines(File log) { + List lines = new ArrayList<>(); + try (BufferedReader reader = new BufferedReader(new FileReader(log))) { + // returns as stream and convert it into a List + boolean first = false; + Iterator i = reader.lines().iterator(); + while (i.hasNext()) { + String line = i.next(); + // read stack trace inside the first and the second hyphens + // '---' + if (line.startsWith(HYPHENS)) { + if (!first) { + first = true; + } else { + break; + } + } else { + lines.add(line); + } + } + reader.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return lines; + } + + /** + * reads a list of lines and saves them to a file + * + * @param lines + * List of lines + * @param out + * path of the file + */ + static File linesToFile(List lines, File out) { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(out))) { + lines.forEach(l -> { + try { + writer.write(l); + writer.newLine(); + } catch (IOException e) { + e.printStackTrace(); + } + }); + writer.close(); + } catch (IOException e) { + e.printStackTrace(); + } + return out; + } + + static void printOptions() { + Collection