diff --git a/.gitignore b/.gitignore index 33c6dc41..f0880588 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,12 @@ bin/ # intellij .idea/ *.iml -lib \ No newline at end of file +lib +output/* + +# binary +jcg +out + +# runtime artifacts +artifacts/results/*/* diff --git a/Docker/Dockerfile b/Docker/Dockerfile new file mode 100755 index 00000000..df5d52c7 --- /dev/null +++ b/Docker/Dockerfile @@ -0,0 +1,84 @@ +FROM ubuntu:22.04 + +# if work dir changed, the RUN sed line needs changes also +WORKDIR /root + +# add needing packages +RUN apt-get update +RUN apt-get -y install git +RUN apt-get install sed +RUN apt-get install patch +RUN apt-get -y install graphviz +RUN apt-get -y install python3 +RUN apt-get -y install python3-pip +RUN apt -y install default-jdk +RUN apt -y install maven +RUN pip3 install setuptools numpy pandas + +RUN mkdir -p -m 700 artifact/ +RUN mkdir -p -m 700 git/ + +# copy java-callgraph and repos +#COPY java-callgraph artifact/ +RUN git clone https://github.com/bitslab/java-callgraph.git git/java-callgraph +RUN cd git/java-callgraph && mvn package +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c mph-table +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c mph-table-fixed +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c mph-table-10 +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c mph-table-50 +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c mph-table-500 +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c mph-table-1000 +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c mph-table-naive +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c convex +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c convex-10 +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c convex-50 +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c convex-500 +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c convex-1000 +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c convex-fixed +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c jflex +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c jflex-10 +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c jflex-50 +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c jflex-500 +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c jflex-1000 +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c jflex-fixed +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c rpki-commons +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c rpki-commons-10 +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c rpki-commons-50 +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c rpki-commons-500 +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c rpki-commons-1000 +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c rpki-commons-fixed +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c mph-table -o mph-table_graph +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c mph-table-fixed -o mph-table-fixed_graph +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c mph-table-10 -o mph-table-10_graph +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c mph-table-50 -o mph-table-50_graph +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c mph-table-500 -o mph-table-500_graph +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c mph-table-1000 -o mph-table-1000_graph +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c mph-table-naive -o mph-table-naive_graph +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c convex -o convex_graph +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c convex-fixed -o convex-fixed_graph +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c convex-10 -o convex-10_graph +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c convex-50 -o convex-50_graph +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c convex-500 -o convex-500_graph +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c convex-1000 -o convex-1000_graph +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c jflex -o jflex_graph +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c jflex-fixed -o jflex-fixed_graph +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c jflex-10 -o jflex-10_graph +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c jflex-50 -o jflex-50_graph +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c jflex-500 -o jflex-500_graph +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c jflex-1000 -o jflex-1000_graph +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c rpki-commons -o rpki-commons_graph +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c rpki-commons-10 -o rpki-commons-10_graph +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c rpki-commons-50 -o rpki-commons-50_graph +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c rpki-commons-500 -o rpki-commons-500_graph +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c rpki-commons-1000 -o rpki-commons-1000_graph +RUN cd git/java-callgraph && java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c rpki-commons-fixed -o rpki-commons-fixed_graph + + +# custom prompt +RUN echo PS1=\"[\\u@artifact-java-callgraph \\W]\\$ \" > .bashrc + +# alter artifact files to point git locally instead +#RUN sed -i 's/https:\/\/github.com\/indeedeng\/mph-table.git/\/root\/git\/mph-table/g' "artifact/artifacts/configs/mph-table/mph-table.yaml" + + +ENTRYPOINT ["/bin/bash"] diff --git a/Docker/README.md b/Docker/README.md new file mode 100644 index 00000000..a5080ff0 --- /dev/null +++ b/Docker/README.md @@ -0,0 +1,26 @@ +# Docker Instructions + +## Setup Instructions +1. Build Docker Image + +`sudo docker build -t jc_docker .` + +![img.png](img.png) + +2. Validate image creation, obtain IMAGE ID + +`sudo docker images` + +![img_1.png](img_1.png) + +3. Interactively run container as root user + +`sudo docker container run -it 83192d10ad58` + +![img_2.png](img_2.png) + +All vanilla and fixed versions of the projects will reside inside the "git/java-callgraph" folder. + + + +## Experiment Instructions diff --git a/Docker/img.png b/Docker/img.png new file mode 100644 index 00000000..d1523799 Binary files /dev/null and b/Docker/img.png differ diff --git a/Docker/img_1.png b/Docker/img_1.png new file mode 100644 index 00000000..5eca9a25 Binary files /dev/null and b/Docker/img_1.png differ diff --git a/Docker/img_2.png b/Docker/img_2.png new file mode 100644 index 00000000..ad69ca7c Binary files /dev/null and b/Docker/img_2.png differ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..bdfcf1d1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM maven:3.8.6-eclipse-temurin-11-alpine + +# if work dir changed, the RUN sed line needs changes also +WORKDIR /root + +# add needing packages +RUN apk add --no-cache git sed patch graphviz + +RUN mkdir -p -m 700 artifact/ +RUN mkdir -p -m 700 git/ + +# copy java-callgraph and repos +COPY git/java-callgraph artifact/ +COPY git/mph-table/ git/mph-table/ + +# custom prompt +RUN echo PS1=\"[\\u@artifact-java-callgraph \\W]\\$ \" > .bashrc + +# alter artifact files to point git locally instead +RUN sed -i 's/https:\/\/github.com\/indeedeng\/mph-table.git/\/root\/git\/mph-table/g' "artifact/artifacts/configs/mph-table/mph-table.yaml" + + +ENTRYPOINT ["/bin/bash"] diff --git a/README.md b/README.md index 48fa2359..20a03959 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # java-callgraph + Static Call Graph Generator for Java Projects ![alt text](output/example-reachability.png) @@ -7,14 +8,16 @@ Static Call Graph Generator for Java Projects - **[Build and Run](#build-and-run)**
- **[Usage](#usage)**
- - **[Example](#example)**
- - **[Graph Structure](#graph-structure)**
- - **[Options](#options)**
+ - **[Example](#example)**
+ - **[Graph Structure](#graph-structure)**
+ - **[Options](#options)**
- **[Known Restrictions](#known-restrictions)**
+## Build and Run -## Build and Run -You must have [Java](https://docs.oracle.com/en/java/javase/11/install/overview-jdk-installation.html#GUID-8677A77F-231A-40F7-98B9-1FD0B48C346A) and [Maven](https://maven.apache.org/install.html) installed +You must +have [Java](https://docs.oracle.com/en/java/javase/11/install/overview-jdk-installation.html#GUID-8677A77F-231A-40F7-98B9-1FD0B48C346A) +and [Maven](https://maven.apache.org/install.html) installed ```console $ git clone git@github.com:wcygan/java-callgraph.git @@ -23,20 +26,25 @@ $ mvn install ``` This will produce a `target` directory with the following jar: -- `javacg-0.1-SNAPSHOT-jar-with-dependencies.jar`: This is an executable jar which includes the static call graph generator and all dependencies needed to run this program + +- `javacg-0.1-SNAPSHOT-jar-with-dependencies.jar`: This is an executable jar which includes the static call graph + generator and all dependencies needed to run this program ## Usage ### Example + After running `mvn install`, you can test this program by running the following code in the root directory: ``` $ java -jar ./target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar -j ./output/java-callgraph-driver-1.0-SNAPSHOT.jar -o example -e "edu.uic.cs398.Main.main([Ljava/lang/String;)V" -c ./output/jacoco.xml ``` -This program will generate a graph and save it to a file `.dot` which you can use [Graphviz](https://www.graphviz.org/download/) to visualize. +This program will generate a graph and save it to a file `.dot` which you can +use [Graphviz](https://www.graphviz.org/download/) to visualize. ### Graph Structure + A directed edge in the graph is denoted with two fully qualified method signatures: ``` @@ -44,6 +52,7 @@ A directed edge in the graph is denoted with two fully qualified method signatur ``` For example: + ``` "edu.uic.cs398.Main.main([Ljava/lang/String;)V" -> "edu.uic.cs398.Book.Book.magic()V" ``` @@ -61,17 +70,28 @@ There are command line options that can be used: | `-a` | Report the ancestry of the entrypoint | `-a` | | `-o` | The name of the output file | `-o example` | -## Known Restrictions +### System Properties +| Property | Usage | Example | +| --- | --- | --- | +| jcg.includeExceptionBasicBlocks | Set to "true" to included exception basic blocks, otherwise these will be excluded by default | -Djcg.includeExceptionBasicBlocks=true | + -* The static call graph generator does not account for methods invoked via - reflection. +#### Heuristic +The "test" phase has been updated to calculate and append a heuristic value to each node. The heuristic value represents the likelihood that a property could be improved +through altering the generator and/or test case. A higher value indicates greater chance of improvement, so the paths with the top three values should be explored first. +The "test" phase will generate another dot file that has the heuristic value prepended to the name of each node. +## Known Restrictions + +* The static call graph generator does not account for methods invoked via reflection. ## Authors Georgios Gousios Will Cygan +Jesse Coultas +Alekh Meka ### License diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 00000000..943ca731 --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,69 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# All Vagrant configuration is done below. The "2" in Vagrant.configure +# configures the configuration version (we support older styles for +# backwards compatibility). Please don't change it unless you know what +# you're doing. +Vagrant.configure("2") do |config| + # The most common configuration options are documented and commented below. + # For a complete reference, please see the online documentation at + # https://docs.vagrantup.com. + + # Every Vagrant development environment requires a box. You can search for + # boxes at https://vagrantcloud.com/search. + config.vm.box = "generic/ubuntu2204" + + # Disable automatic box update checking. If you disable this, then + # boxes will only be checked for updates when the user runs + # `vagrant box outdated`. This is not recommended. + # config.vm.box_check_update = false + + # Create a forwarded port mapping which allows access to a specific port + # within the machine from a port on the host machine. In the example below, + # accessing "localhost:8080" will access port 80 on the guest machine. + # NOTE: This will enable public access to the opened port + # config.vm.network "forwarded_port", guest: 80, host: 8080 + + # Create a forwarded port mapping which allows access to a specific port + # within the machine from a port on the host machine and only allow access + # via 127.0.0.1 to disable public access + # config.vm.network "forwarded_port", guest: 80, host: 8080, host_ip: "127.0.0.1" + + # Create a private network, which allows host-only access to the machine + # using a specific IP. + # config.vm.network "private_network", ip: "192.168.33.10" + + # Create a public network, which generally matched to bridged network. + # Bridged networks make the machine appear as another physical device on + # your network. + # config.vm.network "public_network" + + # Share an additional folder to the guest VM. The first argument is + # the path on the host to the actual folder. The second argument is + # the path on the guest to mount the folder. And the optional third + # argument is a set of non-required options. + # config.vm.synced_folder "../data", "/vagrant_data" + + # Provider-specific configuration so you can fine-tune various + # backing providers for Vagrant. These expose provider-specific options. + # Example for VirtualBox: + # + # config.vm.provider "virtualbox" do |vb| + # # Display the VirtualBox GUI when booting the machine + # vb.gui = true + # + # # Customize the amount of memory on the VM: + # vb.memory = "1024" + # end + # + # View the documentation for the provider you are using for more + # information on available options. + + # Enable provisioning with a shell script. Additional provisioners such as + # Ansible, Chef, Docker, Puppet and Salt are also available. Please see the + # documentation for more information about their specific syntax and use. + # config.vm.provision "shell", inline: <<-SHELL + config.vm.provision :shell, path: "bootstrap.sh" + # SHELL +end diff --git a/artifacts/README.md b/artifacts/README.md new file mode 100644 index 00000000..e80ad178 --- /dev/null +++ b/artifacts/README.md @@ -0,0 +1,44 @@ +## How to use the run configuration: + +Look through the configuration file `config.yaml` and replace each attribute with the attributes corresponding to your +project. + +Let's use [java-callgraph-driver](https://github.com/wcygan/java-callgraph-driver) as an example. + +We specify that we'd like to download the project by using: + +```yaml +repository-url: "git@github.com:wcygan/java-callgraph-driver.git" +``` + +We need to give java-callgraph a hint for how to build the project: + +```yaml +build-system: "maven" +build-command: "mvn install" +``` + +We need to specify where to find certain files once the project is built: + +```yaml +target-jar-location: "/target/java-callgraph-driver-1.0-SNAPSHOT.jar" +coverage-location: "/target/site/jacoco/jacoco.xml" +``` + +Additionally, we can specify the arguments for java-callgraph like so: + +```yaml +entrypoint: "\"edu.uic.cs398.Main.main([Ljava/lang/String;)V\"" +depth: "5" +output-name: "example" +ancestry: "2" +``` + +Once we have the required fields specified, we can run execute the run script like so: + +```shell +python3 run.py +``` + +Please note that this script depends on [PyYAML](https://pypi.org/project/pyaml/) which can be installed with +pip: `pip install pyaml`. \ No newline at end of file diff --git a/artifacts/config.yaml b/artifacts/config.yaml index 7abe426e..6374a669 100644 --- a/artifacts/config.yaml +++ b/artifacts/config.yaml @@ -4,15 +4,13 @@ repository-url: "git@github.com:wcygan/java-callgraph-driver.git" build-system: "maven" build-command: "mvn install" -# Where is Java-Callgraph installed? -javacg-directory: "/Users/wcygan/School/cs398/java-callgraph/" -javacg-jar-location: "target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar" - -# Java-Callgraph options: +# Required Java-Callgraph options: target-jar-location: "/target/java-callgraph-driver-1.0-SNAPSHOT.jar" + +# Optional Java-Callgraph options: +# (Feel free to comment these out) coverage-location: "/target/site/jacoco/jacoco.xml" entrypoint: "\"edu.uic.cs398.Main.main([Ljava/lang/String;)V\"" -depth: "10" +depth: "5" output-name: "example" - - +ancestry: "2" \ No newline at end of file diff --git a/artifacts/configs/convex-10/convex-10.patch b/artifacts/configs/convex-10/convex-10.patch new file mode 100644 index 00000000..351a9625 --- /dev/null +++ b/artifacts/configs/convex-10/convex-10.patch @@ -0,0 +1,118 @@ +diff --git a/convex-core/pom.xml b/convex-core/pom.xml +index 471f81bf..3f83db91 100644 +--- a/convex-core/pom.xml ++++ b/convex-core/pom.xml +@@ -32,6 +32,59 @@ + + + ++ ++ org.jacoco ++ jacoco-maven-plugin ++ 0.8.6 ++ ++ ++ default-prepare-agent ++ ++ prepare-agent ++ ++ ++ ++ jacoco-report ++ test ++ ++ report ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-assembly-plugin ++ 3.3.0 ++ ++ ++ ++ jar-with-dependencies ++ ++ ++ ++ ++ ++ make-assembly ++ package ++ ++ single ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-jar-plugin ++ 3.2.0 ++ ++ ++ ++ test-jar ++ ++ ++ ++ + + + +@@ -44,6 +97,22 @@ + + + ++ ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ ++ ++ ++ report ++ ++ ++ ++ ++ ++ ++ + + + org.bouncycastle +diff --git a/convex-core/src/test/java/convex/comms/GenTestFormat.java b/convex-core/src/test/java/convex/comms/GenTestFormat.java +index 4c8aab37..f123dbe9 100644 +--- a/convex-core/src/test/java/convex/comms/GenTestFormat.java ++++ b/convex-core/src/test/java/convex/comms/GenTestFormat.java +@@ -22,7 +22,7 @@ import convex.test.generators.ValueGen; + + @RunWith(JUnitQuickcheck.class) + public class GenTestFormat { +- @Property ++ @Property(trials = 10) + public void messageRoundTrip(String str) throws BadFormatException { + AString s=Strings.create(str); + Blob b = Format.encodedBlob(s); +@@ -33,7 +33,7 @@ public class GenTestFormat { + FuzzTestFormat.doMutationTest(b); + } + +- @Property ++ @Property(trials = 10) + public void primitiveRoundTrip(@From(PrimitiveGen.class) ACell prim) throws BadFormatException { + Blob b = Format.encodedBlob(prim); + ACell o = Format.read(b); +@@ -43,7 +43,7 @@ public class GenTestFormat { + FuzzTestFormat.doMutationTest(b); + } + +- @Property ++ @Property(trials = 10) + public void dataRoundTrip(@From(ValueGen.class) ACell value) throws BadFormatException { + Ref pref = ACell.createPersisted(value); // ensure persisted + Blob b = Format.encodedBlob(value); diff --git a/artifacts/configs/convex-10/convex-10.yaml b/artifacts/configs/convex-10/convex-10.yaml new file mode 100644 index 00000000..4b478b99 --- /dev/null +++ b/artifacts/configs/convex-10/convex-10.yaml @@ -0,0 +1,16 @@ +name: convex-10 +URL: https://github.com/Convex-Dev/convex.git +checkoutID: e6db05a611cd4a1fb51f959e20d246637bb7744a +patchName: artifacts/configs/convex-10/convex-10.patch +subProject: convex-core +mainJar: convex-core-0.7.1-jar-with-dependencies.jar +testJar: convex-core-0.7.1-tests.jar +#mvnOptions: -DfailIfNoTests=false -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +mvnOptions: -DfailIfNoTests=false +properties: + - name: GenTestFormat#messageRoundTrip + entryPoint: "convex.comms.GenTestFormat.messageRoundTrip(Ljava/lang/String;)V" + - name: GenTestFormat#primitiveRoundTrip + entryPoint: "convex.comms.GenTestFormat.primitiveRoundTrip(Lconvex/core/data/ACell;)V" + - name: GenTestFormat#dataRoundTrip + entryPoint: "convex.comms.GenTestFormat.dataRoundTrip(Lconvex/core/data/ACell;)V" diff --git a/artifacts/configs/convex-1000/convex-1000.patch b/artifacts/configs/convex-1000/convex-1000.patch new file mode 100644 index 00000000..1aaf1308 --- /dev/null +++ b/artifacts/configs/convex-1000/convex-1000.patch @@ -0,0 +1,118 @@ +diff --git a/convex-core/pom.xml b/convex-core/pom.xml +index 471f81bf..3f83db91 100644 +--- a/convex-core/pom.xml ++++ b/convex-core/pom.xml +@@ -32,6 +32,59 @@ + + + ++ ++ org.jacoco ++ jacoco-maven-plugin ++ 0.8.6 ++ ++ ++ default-prepare-agent ++ ++ prepare-agent ++ ++ ++ ++ jacoco-report ++ test ++ ++ report ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-assembly-plugin ++ 3.3.0 ++ ++ ++ ++ jar-with-dependencies ++ ++ ++ ++ ++ ++ make-assembly ++ package ++ ++ single ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-jar-plugin ++ 3.2.0 ++ ++ ++ ++ test-jar ++ ++ ++ ++ + + + +@@ -44,6 +97,22 @@ + + + ++ ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ ++ ++ ++ report ++ ++ ++ ++ ++ ++ ++ + + + org.bouncycastle +diff --git a/convex-core/src/test/java/convex/comms/GenTestFormat.java b/convex-core/src/test/java/convex/comms/GenTestFormat.java +index 4c8aab37..f123dbe9 100644 +--- a/convex-core/src/test/java/convex/comms/GenTestFormat.java ++++ b/convex-core/src/test/java/convex/comms/GenTestFormat.java +@@ -22,7 +22,7 @@ import convex.test.generators.ValueGen; + + @RunWith(JUnitQuickcheck.class) + public class GenTestFormat { +- @Property ++ @Property(trials = 1000) + public void messageRoundTrip(String str) throws BadFormatException { + AString s=Strings.create(str); + Blob b = Format.encodedBlob(s); +@@ -33,7 +33,7 @@ public class GenTestFormat { + FuzzTestFormat.doMutationTest(b); + } + +- @Property ++ @Property(trials = 1000) + public void primitiveRoundTrip(@From(PrimitiveGen.class) ACell prim) throws BadFormatException { + Blob b = Format.encodedBlob(prim); + ACell o = Format.read(b); +@@ -43,7 +43,7 @@ public class GenTestFormat { + FuzzTestFormat.doMutationTest(b); + } + +- @Property ++ @Property(trials = 1000) + public void dataRoundTrip(@From(ValueGen.class) ACell value) throws BadFormatException { + Ref pref = ACell.createPersisted(value); // ensure persisted + Blob b = Format.encodedBlob(value); diff --git a/artifacts/configs/convex-1000/convex-1000.yaml b/artifacts/configs/convex-1000/convex-1000.yaml new file mode 100644 index 00000000..7fa90d90 --- /dev/null +++ b/artifacts/configs/convex-1000/convex-1000.yaml @@ -0,0 +1,16 @@ +name: convex-1000 +URL: https://github.com/Convex-Dev/convex.git +checkoutID: e6db05a611cd4a1fb51f959e20d246637bb7744a +patchName: artifacts/configs/convex-1000/convex-1000.patch +subProject: convex-core +mainJar: convex-core-0.7.1-jar-with-dependencies.jar +testJar: convex-core-0.7.1-tests.jar +#mvnOptions: -DfailIfNoTests=false -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +mvnOptions: -DfailIfNoTests=false +properties: + - name: GenTestFormat#messageRoundTrip + entryPoint: "convex.comms.GenTestFormat.messageRoundTrip(Ljava/lang/String;)V" + - name: GenTestFormat#primitiveRoundTrip + entryPoint: "convex.comms.GenTestFormat.primitiveRoundTrip(Lconvex/core/data/ACell;)V" + - name: GenTestFormat#dataRoundTrip + entryPoint: "convex.comms.GenTestFormat.dataRoundTrip(Lconvex/core/data/ACell;)V" diff --git a/artifacts/configs/convex-50/convex-50.patch b/artifacts/configs/convex-50/convex-50.patch new file mode 100644 index 00000000..12d4e5bc --- /dev/null +++ b/artifacts/configs/convex-50/convex-50.patch @@ -0,0 +1,118 @@ +diff --git a/convex-core/pom.xml b/convex-core/pom.xml +index 471f81bf..3f83db91 100644 +--- a/convex-core/pom.xml ++++ b/convex-core/pom.xml +@@ -32,6 +32,59 @@ + + + ++ ++ org.jacoco ++ jacoco-maven-plugin ++ 0.8.6 ++ ++ ++ default-prepare-agent ++ ++ prepare-agent ++ ++ ++ ++ jacoco-report ++ test ++ ++ report ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-assembly-plugin ++ 3.3.0 ++ ++ ++ ++ jar-with-dependencies ++ ++ ++ ++ ++ ++ make-assembly ++ package ++ ++ single ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-jar-plugin ++ 3.2.0 ++ ++ ++ ++ test-jar ++ ++ ++ ++ + + + +@@ -44,6 +97,22 @@ + + + ++ ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ ++ ++ ++ report ++ ++ ++ ++ ++ ++ ++ + + + org.bouncycastle +diff --git a/convex-core/src/test/java/convex/comms/GenTestFormat.java b/convex-core/src/test/java/convex/comms/GenTestFormat.java +index 4c8aab37..f123dbe9 100644 +--- a/convex-core/src/test/java/convex/comms/GenTestFormat.java ++++ b/convex-core/src/test/java/convex/comms/GenTestFormat.java +@@ -22,7 +22,7 @@ import convex.test.generators.ValueGen; + + @RunWith(JUnitQuickcheck.class) + public class GenTestFormat { +- @Property ++ @Property(trials = 50) + public void messageRoundTrip(String str) throws BadFormatException { + AString s=Strings.create(str); + Blob b = Format.encodedBlob(s); +@@ -33,7 +33,7 @@ public class GenTestFormat { + FuzzTestFormat.doMutationTest(b); + } + +- @Property ++ @Property(trials = 50) + public void primitiveRoundTrip(@From(PrimitiveGen.class) ACell prim) throws BadFormatException { + Blob b = Format.encodedBlob(prim); + ACell o = Format.read(b); +@@ -43,7 +43,7 @@ public class GenTestFormat { + FuzzTestFormat.doMutationTest(b); + } + +- @Property ++ @Property(trials = 50) + public void dataRoundTrip(@From(ValueGen.class) ACell value) throws BadFormatException { + Ref pref = ACell.createPersisted(value); // ensure persisted + Blob b = Format.encodedBlob(value); diff --git a/artifacts/configs/convex-50/convex-50.yaml b/artifacts/configs/convex-50/convex-50.yaml new file mode 100644 index 00000000..40b887a3 --- /dev/null +++ b/artifacts/configs/convex-50/convex-50.yaml @@ -0,0 +1,16 @@ +name: convex-50 +URL: https://github.com/Convex-Dev/convex.git +checkoutID: e6db05a611cd4a1fb51f959e20d246637bb7744a +patchName: artifacts/configs/convex-50/convex-50.patch +subProject: convex-core +mainJar: convex-core-0.7.1-jar-with-dependencies.jar +testJar: convex-core-0.7.1-tests.jar +#mvnOptions: -DfailIfNoTests=false -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +mvnOptions: -DfailIfNoTests=false +properties: + - name: GenTestFormat#messageRoundTrip + entryPoint: "convex.comms.GenTestFormat.messageRoundTrip(Ljava/lang/String;)V" + - name: GenTestFormat#primitiveRoundTrip + entryPoint: "convex.comms.GenTestFormat.primitiveRoundTrip(Lconvex/core/data/ACell;)V" + - name: GenTestFormat#dataRoundTrip + entryPoint: "convex.comms.GenTestFormat.dataRoundTrip(Lconvex/core/data/ACell;)V" diff --git a/artifacts/configs/convex-500/convex-500.patch b/artifacts/configs/convex-500/convex-500.patch new file mode 100644 index 00000000..924acac3 --- /dev/null +++ b/artifacts/configs/convex-500/convex-500.patch @@ -0,0 +1,118 @@ +diff --git a/convex-core/pom.xml b/convex-core/pom.xml +index 471f81bf..3f83db91 100644 +--- a/convex-core/pom.xml ++++ b/convex-core/pom.xml +@@ -32,6 +32,59 @@ + + + ++ ++ org.jacoco ++ jacoco-maven-plugin ++ 0.8.6 ++ ++ ++ default-prepare-agent ++ ++ prepare-agent ++ ++ ++ ++ jacoco-report ++ test ++ ++ report ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-assembly-plugin ++ 3.3.0 ++ ++ ++ ++ jar-with-dependencies ++ ++ ++ ++ ++ ++ make-assembly ++ package ++ ++ single ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-jar-plugin ++ 3.2.0 ++ ++ ++ ++ test-jar ++ ++ ++ ++ + + + +@@ -44,6 +97,22 @@ + + + ++ ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ ++ ++ ++ report ++ ++ ++ ++ ++ ++ ++ + + + org.bouncycastle +diff --git a/convex-core/src/test/java/convex/comms/GenTestFormat.java b/convex-core/src/test/java/convex/comms/GenTestFormat.java +index 4c8aab37..f123dbe9 100644 +--- a/convex-core/src/test/java/convex/comms/GenTestFormat.java ++++ b/convex-core/src/test/java/convex/comms/GenTestFormat.java +@@ -22,7 +22,7 @@ import convex.test.generators.ValueGen; + + @RunWith(JUnitQuickcheck.class) + public class GenTestFormat { +- @Property ++ @Property(trials = 500) + public void messageRoundTrip(String str) throws BadFormatException { + AString s=Strings.create(str); + Blob b = Format.encodedBlob(s); +@@ -33,7 +33,7 @@ public class GenTestFormat { + FuzzTestFormat.doMutationTest(b); + } + +- @Property ++ @Property(trials = 500) + public void primitiveRoundTrip(@From(PrimitiveGen.class) ACell prim) throws BadFormatException { + Blob b = Format.encodedBlob(prim); + ACell o = Format.read(b); +@@ -43,7 +43,7 @@ public class GenTestFormat { + FuzzTestFormat.doMutationTest(b); + } + +- @Property ++ @Property(trials = 500) + public void dataRoundTrip(@From(ValueGen.class) ACell value) throws BadFormatException { + Ref pref = ACell.createPersisted(value); // ensure persisted + Blob b = Format.encodedBlob(value); diff --git a/artifacts/configs/convex-500/convex-500.yaml b/artifacts/configs/convex-500/convex-500.yaml new file mode 100644 index 00000000..f1e6d2cc --- /dev/null +++ b/artifacts/configs/convex-500/convex-500.yaml @@ -0,0 +1,16 @@ +name: convex-500 +URL: https://github.com/Convex-Dev/convex.git +checkoutID: e6db05a611cd4a1fb51f959e20d246637bb7744a +patchName: artifacts/configs/convex-500/convex-500.patch +subProject: convex-core +mainJar: convex-core-0.7.1-jar-with-dependencies.jar +testJar: convex-core-0.7.1-tests.jar +#mvnOptions: -DfailIfNoTests=false -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +mvnOptions: -DfailIfNoTests=false +properties: + - name: GenTestFormat#messageRoundTrip + entryPoint: "convex.comms.GenTestFormat.messageRoundTrip(Ljava/lang/String;)V" + - name: GenTestFormat#primitiveRoundTrip + entryPoint: "convex.comms.GenTestFormat.primitiveRoundTrip(Lconvex/core/data/ACell;)V" + - name: GenTestFormat#dataRoundTrip + entryPoint: "convex.comms.GenTestFormat.dataRoundTrip(Lconvex/core/data/ACell;)V" diff --git a/artifacts/configs/convex-fixed/convex-fixed.patch b/artifacts/configs/convex-fixed/convex-fixed.patch new file mode 100644 index 00000000..0f57b637 --- /dev/null +++ b/artifacts/configs/convex-fixed/convex-fixed.patch @@ -0,0 +1,156 @@ +diff --git a/convex-core/pom.xml b/convex-core/pom.xml +index 471f81bf..3f83db91 100644 +--- a/convex-core/pom.xml ++++ b/convex-core/pom.xml +@@ -32,6 +32,59 @@ + + + ++ ++ org.jacoco ++ jacoco-maven-plugin ++ 0.8.6 ++ ++ ++ default-prepare-agent ++ ++ prepare-agent ++ ++ ++ ++ jacoco-report ++ test ++ ++ report ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-assembly-plugin ++ 3.3.0 ++ ++ ++ ++ jar-with-dependencies ++ ++ ++ ++ ++ ++ make-assembly ++ package ++ ++ single ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-jar-plugin ++ 3.2.0 ++ ++ ++ ++ test-jar ++ ++ ++ ++ + + + +@@ -44,6 +97,22 @@ + + + ++ ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ ++ ++ ++ report ++ ++ ++ ++ ++ ++ ++ + + + org.bouncycastle +diff --git a/convex-core/src/test/java/convex/test/generators/PrimitiveGen.java b/convex-core/src/test/java/convex/test/generators/PrimitiveGen.java +index 1eba23e1..045f17d6 100644 +--- a/convex-core/src/test/java/convex/test/generators/PrimitiveGen.java ++++ b/convex-core/src/test/java/convex/test/generators/PrimitiveGen.java +@@ -5,6 +5,7 @@ import com.pholser.junit.quickcheck.generator.Generator; + import com.pholser.junit.quickcheck.random.SourceOfRandomness; + + import convex.core.data.ACell; ++import convex.core.data.Blob; + import convex.core.data.prim.CVMBool; + import convex.core.data.prim.CVMByte; + import convex.core.data.prim.CVMChar; +@@ -16,6 +17,8 @@ import convex.core.data.prim.CVMLong; + */ + public class PrimitiveGen extends Generator { + public final static PrimitiveGen INSTANCE = new PrimitiveGen(); ++ private static final int ONE_KB = 1024; ++ private static final int ONE_MB = ONE_KB * 1024; + + // public final Generator BYTE = gen().type(byte.class); + +@@ -25,7 +28,7 @@ public class PrimitiveGen extends Generator { + + @Override + public ACell generate(SourceOfRandomness r, GenerationStatus status) { +- int type = r.nextInt(6); ++ int type = r.nextInt(7); + switch (type) { + case 0: + return null; +@@ -39,8 +42,38 @@ public class PrimitiveGen extends Generator { + return CVMDouble.create(r.nextDouble()); + case 5: + return CVMBool.create(r.nextBoolean()); ++ case 6: ++ return Blob.create(r.nextBytes(getByteSize(r))); + default: + throw new Error("Unexpected type: " + type); + } + } ++ ++ private static int getByteSize(SourceOfRandomness r) { ++ int rnd = r.nextInt(1, 100); ++ ++ // 1% change of getting number 0 ++ if (rnd == 1) { ++ return 0; ++ } ++ ++ // 1% change of getting number 1 ++ if (rnd == 2) { ++ return 1; ++ } ++ ++ // 15% chance of getting a "larger" size ++ if (rnd >= 3 && rnd <= 17) { ++ return r.nextInt(ONE_KB * 4, ONE_KB * 100); ++ } ++ ++ // 5% chance of getting a "huge" size ++ if (rnd >= 18 && rnd <= 22) { ++ return r.nextInt(ONE_KB * 100, ONE_MB); ++ } ++ ++ // 78% - normalish size ++ return r.nextInt(2, ONE_KB * 4); ++ } ++ + } diff --git a/artifacts/configs/convex-fixed/convex-fixed.yaml b/artifacts/configs/convex-fixed/convex-fixed.yaml new file mode 100644 index 00000000..876294ab --- /dev/null +++ b/artifacts/configs/convex-fixed/convex-fixed.yaml @@ -0,0 +1,16 @@ +name: convex-fixed +URL: https://github.com/Convex-Dev/convex.git +checkoutID: e6db05a611cd4a1fb51f959e20d246637bb7744a +patchName: artifacts/configs/convex-fixed/convex-fixed.patch +subProject: convex-core +mainJar: convex-core-0.7.1-jar-with-dependencies.jar +testJar: convex-core-0.7.1-tests.jar +#mvnOptions: -DfailIfNoTests=false -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +mvnOptions: -DfailIfNoTests=false +properties: + - name: GenTestFormat#messageRoundTrip + entryPoint: "convex.comms.GenTestFormat.messageRoundTrip(Ljava/lang/String;)V" +# - name: GenTestFormat#primitiveRoundTrip +# entryPoint: "convex.comms.GenTestFormat.primitiveRoundTrip(Lconvex/core/data/ACell;)V" +# - name: GenTestFormat#dataRoundTrip +# entryPoint: "convex.comms.GenTestFormat.dataRoundTrip(Lconvex/core/data/ACell;)V" diff --git a/artifacts/configs/convex/convex.patch b/artifacts/configs/convex/convex.patch new file mode 100644 index 00000000..811570d8 --- /dev/null +++ b/artifacts/configs/convex/convex.patch @@ -0,0 +1,87 @@ +diff --git a/convex-core/pom.xml b/convex-core/pom.xml +index 471f81bf..3f83db91 100644 +--- a/convex-core/pom.xml ++++ b/convex-core/pom.xml +@@ -32,6 +32,59 @@ + + + ++ ++ org.jacoco ++ jacoco-maven-plugin ++ 0.8.6 ++ ++ ++ default-prepare-agent ++ ++ prepare-agent ++ ++ ++ ++ jacoco-report ++ test ++ ++ report ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-assembly-plugin ++ 3.3.0 ++ ++ ++ ++ jar-with-dependencies ++ ++ ++ ++ ++ ++ make-assembly ++ package ++ ++ single ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-jar-plugin ++ 3.2.0 ++ ++ ++ ++ test-jar ++ ++ ++ ++ + + + +@@ -44,6 +97,22 @@ + + + ++ ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ ++ ++ ++ report ++ ++ ++ ++ ++ ++ ++ + + + org.bouncycastle diff --git a/artifacts/configs/convex/convex.yaml b/artifacts/configs/convex/convex.yaml new file mode 100644 index 00000000..1e949800 --- /dev/null +++ b/artifacts/configs/convex/convex.yaml @@ -0,0 +1,16 @@ +name: convex +URL: https://github.com/Convex-Dev/convex.git +checkoutID: e6db05a611cd4a1fb51f959e20d246637bb7744a +patchName: artifacts/configs/convex/convex.patch +subProject: convex-core +mainJar: convex-core-0.7.1-jar-with-dependencies.jar +testJar: convex-core-0.7.1-tests.jar +#mvnOptions: -DfailIfNoTests=false -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +mvnOptions: -DfailIfNoTests=false +properties: + - name: GenTestFormat#messageRoundTrip + entryPoint: "convex.comms.GenTestFormat.messageRoundTrip(Ljava/lang/String;)V" + - name: GenTestFormat#primitiveRoundTrip + entryPoint: "convex.comms.GenTestFormat.primitiveRoundTrip(Lconvex/core/data/ACell;)V" + - name: GenTestFormat#dataRoundTrip + entryPoint: "convex.comms.GenTestFormat.dataRoundTrip(Lconvex/core/data/ACell;)V" diff --git a/artifacts/configs/jflex-10/jflex-10.patch b/artifacts/configs/jflex-10/jflex-10.patch new file mode 100644 index 00000000..9bbdb95d --- /dev/null +++ b/artifacts/configs/jflex-10/jflex-10.patch @@ -0,0 +1,146 @@ +diff --git a/jflex/pom.xml b/jflex/pom.xml +index 47904b61..185a3790 100644 +--- a/jflex/pom.xml ++++ b/jflex/pom.xml +@@ -51,6 +51,21 @@ + +1 + + ++ ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ ++ ++ ++ report ++ ++ ++ ++ ++ ++ + + + com.github.vbmacher +@@ -156,6 +171,34 @@ + + + ++ ++ ++ ++ test-jar ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-assembly-plugin ++ 3.3.0 ++ ++ ++ ++ jar-with-dependencies ++ ++ ++ ++ ++ ++ make-assembly ++ package ++ ++ single ++ ++ ++ + + + org.apache.maven.plugins +@@ -231,6 +274,13 @@ + + + ++ ++ jacoco-report ++ test ++ ++ report ++ ++ + + + +diff --git a/jflex/src/test/java/jflex/core/unicode/CharClassesQuickcheck.java b/jflex/src/test/java/jflex/core/unicode/CharClassesQuickcheck.java +index c31b5221..4744c3ce 100644 +--- a/jflex/src/test/java/jflex/core/unicode/CharClassesQuickcheck.java ++++ b/jflex/src/test/java/jflex/core/unicode/CharClassesQuickcheck.java +@@ -45,7 +45,7 @@ public class CharClassesQuickcheck { + assertThat(c.getMaxCharCode()).isEqualTo(CharClasses.maxChar); + } + +- @Property ++ @Property(trials = 10) + public void addSingle( + CharClasses classes, + @InRange(minInt = 0, maxInt = CharClasses.maxChar) int c1, +@@ -56,7 +56,7 @@ public class CharClassesQuickcheck { + assertThat(classes.getClassCode(c1)).isNotEqualTo(classes.getClassCode(c2)); + } + +- @Property ++ @Property(trials = 10) + public void addSingleSingleton( + CharClasses classes, @InRange(minInt = 0, maxInt = CharClasses.maxChar) int c) { + classes.makeClass(c, false); +@@ -64,7 +64,7 @@ public class CharClassesQuickcheck { + assertThat(set).isEqualTo(IntCharSet.ofCharacter(c)); + } + +- @Property ++ @Property(trials = 10) + public void addSet( + CharClasses classes, + @InRange(maxInt = CharClasses.maxChar) IntCharSet set, +@@ -110,7 +110,7 @@ public class CharClassesQuickcheck { + assertThat(others).isEqualTo(IntCharSet.complementOf(set)); + } + +- @Property ++ @Property(trials = 10) + public void addString( + CharClasses classes, String s, @InRange(minInt = 0, maxInt = CharClasses.maxChar) int c) { + +diff --git a/jflex/src/test/java/jflex/state/StateSetQuickcheck.java b/jflex/src/test/java/jflex/state/StateSetQuickcheck.java +index c3ac7e67..08945ece 100644 +--- a/jflex/src/test/java/jflex/state/StateSetQuickcheck.java ++++ b/jflex/src/test/java/jflex/state/StateSetQuickcheck.java +@@ -154,7 +154,7 @@ public class StateSetQuickcheck { + assertThat(s.hasElement(e)).isFalse(); + } + +- @Property ++ @Property(trials = 10) + public void removeAdd( + @Size(max = 90) @InRange(minInt = 0, maxInt = 100) StateSet s, + @InRange(minInt = 0, maxInt = 100) int e) { +@@ -180,7 +180,7 @@ public class StateSetQuickcheck { + assertThat(set.hasElement(e)).isTrue(); + } + +- @Property ++ @Property(trials = 10) + public void addStateDoesNotRemove(StateSet set, @InRange(minInt = 0, maxInt = 2 ^ 32) int e) { + StateSet setPre = new StateSet(set); + set.addState(e); +@@ -224,7 +224,7 @@ public class StateSetQuickcheck { + assertThat(union1).isEqualTo(union0); + } + +- @Property ++ @Property(trials = 10) + public void containsElements(StateSet s, @InRange(minInt = 0, maxInt = 2 ^ 32) int e) { + s.addState(e); + assertThat(s.containsElements()).isTrue(); diff --git a/artifacts/configs/jflex-10/jflex-10.yaml b/artifacts/configs/jflex-10/jflex-10.yaml new file mode 100644 index 00000000..ea2dab7e --- /dev/null +++ b/artifacts/configs/jflex-10/jflex-10.yaml @@ -0,0 +1,100 @@ +name: jflex-10 +URL: https://github.com/jflex-de/jflex.git +checkoutID: e6d1752bd48a7ccb2a2b78479dc5a73ac475bbb9 +patchName: artifacts/configs/jflex-10/jflex-10.patch +subProject: jflex +mainJar: jflex-1.8.2-jar-with-dependencies.jar +testJar: jflex-1.8.2-tests.jar +#mvnOptions: -DfailIfNoTests=false -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +mvnOptions: -DfailIfNoTests=false +properties: + - name: StateSetQuickcheck#removeAdd + entryPoint: "jflex.state.StateSetQuickcheck.removeAdd(Ljflex/state/StateSet;I)V" + - name: StateSetQuickcheck#addStateDoesNotRemove + entryPoint: "jflex.state.StateSetQuickcheck.addStateDoesNotRemove(Ljflex/state/StateSet;I)V" + - name: StateSetQuickcheck#containsElements + entryPoint: "jflex.state.StateSetQuickcheck.containsElements(Ljflex/state/StateSet;I)V" + - name: CharClassesQuickcheck#addSingle + entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSingle(Ljflex/core/unicode/CharClasses;II)V" + - name: CharClassesQuickcheck#addSingleSingleton + entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSingleSingleton(Ljflex/core/unicode/CharClasses;I)V" + - name: CharClassesQuickcheck#addSet + entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSet(Ljflex/core/unicode/CharClasses;Ljflex/core/unicode/IntCharSet;I)V" + - name: CharClassesQuickcheck#addString + entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addString(Ljflex/core/unicode/CharClasses;Ljava/lang/String;I)V" +# - name: StateSetQuickcheck#size2nbits +# entryPoint: "jflex.state.StateSetQuickcheck.size2nbits(I)V" +# - name: StateSetQuickcheck#containsIsSubset +# entryPoint: "jflex.state.StateSetQuickcheck.containsIsSubset(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addIsUnion +# entryPoint: "jflex.state.StateSetQuickcheck.addIsUnion(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addCommutes +# entryPoint: "jflex.state.StateSetQuickcheck.addCommutes(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addEmpty +# entryPoint: "jflex.state.StateSetQuickcheck.addEmpty(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addSelf +# entryPoint: "jflex.state.StateSetQuickcheck.addSelf(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addIdemPotent +# entryPoint: "jflex.state.StateSetQuickcheck.addIdemPotent(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersect +# entryPoint: "jflex.state.StateSetQuickcheck.intersect(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersectUnchanged +# entryPoint: "jflex.state.StateSetQuickcheck.intersectUnchanged(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersectCommutes +# entryPoint: "jflex.state.StateSetQuickcheck.intersectCommutes(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersectEmpty +# entryPoint: "jflex.state.StateSetQuickcheck.intersectEmpty(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersectSelf +# entryPoint: "jflex.state.StateSetQuickcheck.intersectSelf(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#containsItsElements +# entryPoint: "jflex.state.StateSetQuickcheck.containsItsElements(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#removeRemoves +# entryPoint: "jflex.state.StateSetQuickcheck.removeRemoves(Ljflex/state/StateSet;I)V" +# - name: StateSetQuickcheck#clearMakesEmpty +# entryPoint: "jflex.state.StateSetQuickcheck.clearMakesEmpty(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addStateAdds +# entryPoint: "jflex.state.StateSetQuickcheck.addStateAdds(Ljflex/state/StateSet;I)V" +# - name: StateSetQuickcheck#addStateAdd +# entryPoint: "jflex.state.StateSetQuickcheck.addStateAdd(Ljflex/state/StateSet;I)V" +# - name: StateSetQuickcheck#complementNoOriginalElements +# entryPoint: "jflex.state.StateSetQuickcheck.complementNoOriginalElements(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#complementElements +# entryPoint: "jflex.state.StateSetQuickcheck.complementElements(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#complementUnion +# entryPoint: "jflex.state.StateSetQuickcheck.complementUnion(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#containsNoElements +# entryPoint: "jflex.state.StateSetQuickcheck.containsNoElements(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#copy +# entryPoint: "jflex.state.StateSetQuickcheck.copy(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#copyInto +# entryPoint: "jflex.state.StateSetQuickcheck.copyInto(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#hashCode +# entryPoint: "jflex.state.StateSetQuickcheck.hashCode(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#getAndRemoveRemoves +# entryPoint: "jflex.state.StateSetQuickcheck.getAndRemoveRemoves(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#getAndRemoveIsElement +# entryPoint: "jflex.state.StateSetQuickcheck.getAndRemoveIsElement(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#getAndRemoveAdd +# entryPoint: "jflex.state.StateSetQuickcheck.getAndRemoveAdd(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#enumerator +# entryPoint: "jflex.state.StateSetQuickcheck.enumerator(Ljflex/state/StateSet;)V" +# - name: CharClassesQuickcheck#invariants +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.invariants(Ljflex/core/unicode/CharClasses;)V" +# - name: CharClassesQuickcheck#maxCharCode +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.maxCharCode(Ljflex/core/unicode/CharClasses;)V" +# - name: CharClassesQuickcheck#addSetParts +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSetParts(Ljflex/core/unicode/CharClasses;Ljflex/core/unicode/IntCharSet;)V" +# - name: CharClassesQuickcheck#addSetComplement +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSetComplement(Ljflex/core/unicode/CharClasses;Ljflex/core/unicode/IntCharSet;)V" +# - name: CharClassesQuickcheck#normaliseSingle +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.normaliseSingle(Ljflex/core/unicode/CharClasses;I)V" +# - name: CharClassesQuickcheck#computeTablesEq +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.computeTablesEq(Ljflex/core/unicode/CharClasses;Ljava/util/ArrayList;)V" +# - name: CharClassesQuickcheck#getTablesEq +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.getTablesEq(Ljflex/core/unicode/CharClasses;Ljava/util/ArrayList;)V" +# - name: CharClassesQuickcheck#classCodesUnion +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.classCodesUnion(Ljflex/core/unicode/CharClasses;)V" +# - name: CharClassesQuickcheck#classCodesCode +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.classCodesCode(Ljflex/core/unicode/CharClasses;)V" +# - name: CharClassesQuickcheck#classCodesDisjointOrdered +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.classCodesDisjointOrdered(Ljflex/core/unicode/CharClasses;)V" diff --git a/artifacts/configs/jflex-1000/jflex-1000.patch b/artifacts/configs/jflex-1000/jflex-1000.patch new file mode 100644 index 00000000..7d7b370d --- /dev/null +++ b/artifacts/configs/jflex-1000/jflex-1000.patch @@ -0,0 +1,146 @@ +diff --git a/jflex/pom.xml b/jflex/pom.xml +index 47904b61..185a3790 100644 +--- a/jflex/pom.xml ++++ b/jflex/pom.xml +@@ -51,6 +51,21 @@ + +1 + + ++ ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ ++ ++ ++ report ++ ++ ++ ++ ++ ++ + + + com.github.vbmacher +@@ -156,6 +171,34 @@ + + + ++ ++ ++ ++ test-jar ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-assembly-plugin ++ 3.3.0 ++ ++ ++ ++ jar-with-dependencies ++ ++ ++ ++ ++ ++ make-assembly ++ package ++ ++ single ++ ++ ++ + + + org.apache.maven.plugins +@@ -231,6 +274,13 @@ + + + ++ ++ jacoco-report ++ test ++ ++ report ++ ++ + + + +diff --git a/jflex/src/test/java/jflex/core/unicode/CharClassesQuickcheck.java b/jflex/src/test/java/jflex/core/unicode/CharClassesQuickcheck.java +index c31b5221..4744c3ce 100644 +--- a/jflex/src/test/java/jflex/core/unicode/CharClassesQuickcheck.java ++++ b/jflex/src/test/java/jflex/core/unicode/CharClassesQuickcheck.java +@@ -45,7 +45,7 @@ public class CharClassesQuickcheck { + assertThat(c.getMaxCharCode()).isEqualTo(CharClasses.maxChar); + } + +- @Property ++ @Property(trials = 1000) + public void addSingle( + CharClasses classes, + @InRange(minInt = 0, maxInt = CharClasses.maxChar) int c1, +@@ -56,7 +56,7 @@ public class CharClassesQuickcheck { + assertThat(classes.getClassCode(c1)).isNotEqualTo(classes.getClassCode(c2)); + } + +- @Property ++ @Property(trials = 1000) + public void addSingleSingleton( + CharClasses classes, @InRange(minInt = 0, maxInt = CharClasses.maxChar) int c) { + classes.makeClass(c, false); +@@ -64,7 +64,7 @@ public class CharClassesQuickcheck { + assertThat(set).isEqualTo(IntCharSet.ofCharacter(c)); + } + +- @Property ++ @Property(trials = 1000) + public void addSet( + CharClasses classes, + @InRange(maxInt = CharClasses.maxChar) IntCharSet set, +@@ -110,7 +110,7 @@ public class CharClassesQuickcheck { + assertThat(others).isEqualTo(IntCharSet.complementOf(set)); + } + +- @Property ++ @Property(trials = 1000) + public void addString( + CharClasses classes, String s, @InRange(minInt = 0, maxInt = CharClasses.maxChar) int c) { + +diff --git a/jflex/src/test/java/jflex/state/StateSetQuickcheck.java b/jflex/src/test/java/jflex/state/StateSetQuickcheck.java +index c3ac7e67..08945ece 100644 +--- a/jflex/src/test/java/jflex/state/StateSetQuickcheck.java ++++ b/jflex/src/test/java/jflex/state/StateSetQuickcheck.java +@@ -154,7 +154,7 @@ public class StateSetQuickcheck { + assertThat(s.hasElement(e)).isFalse(); + } + +- @Property ++ @Property(trials = 1000) + public void removeAdd( + @Size(max = 90) @InRange(minInt = 0, maxInt = 100) StateSet s, + @InRange(minInt = 0, maxInt = 100) int e) { +@@ -180,7 +180,7 @@ public class StateSetQuickcheck { + assertThat(set.hasElement(e)).isTrue(); + } + +- @Property ++ @Property(trials = 1000) + public void addStateDoesNotRemove(StateSet set, @InRange(minInt = 0, maxInt = 2 ^ 32) int e) { + StateSet setPre = new StateSet(set); + set.addState(e); +@@ -224,7 +224,7 @@ public class StateSetQuickcheck { + assertThat(union1).isEqualTo(union0); + } + +- @Property ++ @Property(trials = 1000) + public void containsElements(StateSet s, @InRange(minInt = 0, maxInt = 2 ^ 32) int e) { + s.addState(e); + assertThat(s.containsElements()).isTrue(); diff --git a/artifacts/configs/jflex-1000/jflex-1000.yaml b/artifacts/configs/jflex-1000/jflex-1000.yaml new file mode 100644 index 00000000..7edccbaa --- /dev/null +++ b/artifacts/configs/jflex-1000/jflex-1000.yaml @@ -0,0 +1,100 @@ +name: jflex-1000 +URL: https://github.com/jflex-de/jflex.git +checkoutID: e6d1752bd48a7ccb2a2b78479dc5a73ac475bbb9 +patchName: artifacts/configs/jflex-1000/jflex-1000.patch +subProject: jflex +mainJar: jflex-1.8.2-jar-with-dependencies.jar +testJar: jflex-1.8.2-tests.jar +#mvnOptions: -DfailIfNoTests=false -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +mvnOptions: -DfailIfNoTests=false +properties: + - name: StateSetQuickcheck#removeAdd + entryPoint: "jflex.state.StateSetQuickcheck.removeAdd(Ljflex/state/StateSet;I)V" + - name: StateSetQuickcheck#addStateDoesNotRemove + entryPoint: "jflex.state.StateSetQuickcheck.addStateDoesNotRemove(Ljflex/state/StateSet;I)V" + - name: StateSetQuickcheck#containsElements + entryPoint: "jflex.state.StateSetQuickcheck.containsElements(Ljflex/state/StateSet;I)V" + - name: CharClassesQuickcheck#addSingle + entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSingle(Ljflex/core/unicode/CharClasses;II)V" + - name: CharClassesQuickcheck#addSingleSingleton + entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSingleSingleton(Ljflex/core/unicode/CharClasses;I)V" + - name: CharClassesQuickcheck#addSet + entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSet(Ljflex/core/unicode/CharClasses;Ljflex/core/unicode/IntCharSet;I)V" + - name: CharClassesQuickcheck#addString + entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addString(Ljflex/core/unicode/CharClasses;Ljava/lang/String;I)V" +# - name: StateSetQuickcheck#size2nbits +# entryPoint: "jflex.state.StateSetQuickcheck.size2nbits(I)V" +# - name: StateSetQuickcheck#containsIsSubset +# entryPoint: "jflex.state.StateSetQuickcheck.containsIsSubset(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addIsUnion +# entryPoint: "jflex.state.StateSetQuickcheck.addIsUnion(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addCommutes +# entryPoint: "jflex.state.StateSetQuickcheck.addCommutes(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addEmpty +# entryPoint: "jflex.state.StateSetQuickcheck.addEmpty(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addSelf +# entryPoint: "jflex.state.StateSetQuickcheck.addSelf(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addIdemPotent +# entryPoint: "jflex.state.StateSetQuickcheck.addIdemPotent(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersect +# entryPoint: "jflex.state.StateSetQuickcheck.intersect(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersectUnchanged +# entryPoint: "jflex.state.StateSetQuickcheck.intersectUnchanged(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersectCommutes +# entryPoint: "jflex.state.StateSetQuickcheck.intersectCommutes(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersectEmpty +# entryPoint: "jflex.state.StateSetQuickcheck.intersectEmpty(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersectSelf +# entryPoint: "jflex.state.StateSetQuickcheck.intersectSelf(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#containsItsElements +# entryPoint: "jflex.state.StateSetQuickcheck.containsItsElements(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#removeRemoves +# entryPoint: "jflex.state.StateSetQuickcheck.removeRemoves(Ljflex/state/StateSet;I)V" +# - name: StateSetQuickcheck#clearMakesEmpty +# entryPoint: "jflex.state.StateSetQuickcheck.clearMakesEmpty(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addStateAdds +# entryPoint: "jflex.state.StateSetQuickcheck.addStateAdds(Ljflex/state/StateSet;I)V" +# - name: StateSetQuickcheck#addStateAdd +# entryPoint: "jflex.state.StateSetQuickcheck.addStateAdd(Ljflex/state/StateSet;I)V" +# - name: StateSetQuickcheck#complementNoOriginalElements +# entryPoint: "jflex.state.StateSetQuickcheck.complementNoOriginalElements(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#complementElements +# entryPoint: "jflex.state.StateSetQuickcheck.complementElements(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#complementUnion +# entryPoint: "jflex.state.StateSetQuickcheck.complementUnion(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#containsNoElements +# entryPoint: "jflex.state.StateSetQuickcheck.containsNoElements(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#copy +# entryPoint: "jflex.state.StateSetQuickcheck.copy(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#copyInto +# entryPoint: "jflex.state.StateSetQuickcheck.copyInto(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#hashCode +# entryPoint: "jflex.state.StateSetQuickcheck.hashCode(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#getAndRemoveRemoves +# entryPoint: "jflex.state.StateSetQuickcheck.getAndRemoveRemoves(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#getAndRemoveIsElement +# entryPoint: "jflex.state.StateSetQuickcheck.getAndRemoveIsElement(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#getAndRemoveAdd +# entryPoint: "jflex.state.StateSetQuickcheck.getAndRemoveAdd(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#enumerator +# entryPoint: "jflex.state.StateSetQuickcheck.enumerator(Ljflex/state/StateSet;)V" +# - name: CharClassesQuickcheck#invariants +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.invariants(Ljflex/core/unicode/CharClasses;)V" +# - name: CharClassesQuickcheck#maxCharCode +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.maxCharCode(Ljflex/core/unicode/CharClasses;)V" +# - name: CharClassesQuickcheck#addSetParts +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSetParts(Ljflex/core/unicode/CharClasses;Ljflex/core/unicode/IntCharSet;)V" +# - name: CharClassesQuickcheck#addSetComplement +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSetComplement(Ljflex/core/unicode/CharClasses;Ljflex/core/unicode/IntCharSet;)V" +# - name: CharClassesQuickcheck#normaliseSingle +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.normaliseSingle(Ljflex/core/unicode/CharClasses;I)V" +# - name: CharClassesQuickcheck#computeTablesEq +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.computeTablesEq(Ljflex/core/unicode/CharClasses;Ljava/util/ArrayList;)V" +# - name: CharClassesQuickcheck#getTablesEq +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.getTablesEq(Ljflex/core/unicode/CharClasses;Ljava/util/ArrayList;)V" +# - name: CharClassesQuickcheck#classCodesUnion +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.classCodesUnion(Ljflex/core/unicode/CharClasses;)V" +# - name: CharClassesQuickcheck#classCodesCode +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.classCodesCode(Ljflex/core/unicode/CharClasses;)V" +# - name: CharClassesQuickcheck#classCodesDisjointOrdered +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.classCodesDisjointOrdered(Ljflex/core/unicode/CharClasses;)V" diff --git a/artifacts/configs/jflex-50/jflex-50.patch b/artifacts/configs/jflex-50/jflex-50.patch new file mode 100644 index 00000000..51dc35a2 --- /dev/null +++ b/artifacts/configs/jflex-50/jflex-50.patch @@ -0,0 +1,146 @@ +diff --git a/jflex/pom.xml b/jflex/pom.xml +index 47904b61..185a3790 100644 +--- a/jflex/pom.xml ++++ b/jflex/pom.xml +@@ -51,6 +51,21 @@ + +1 + + ++ ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ ++ ++ ++ report ++ ++ ++ ++ ++ ++ + + + com.github.vbmacher +@@ -156,6 +171,34 @@ + + + ++ ++ ++ ++ test-jar ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-assembly-plugin ++ 3.3.0 ++ ++ ++ ++ jar-with-dependencies ++ ++ ++ ++ ++ ++ make-assembly ++ package ++ ++ single ++ ++ ++ + + + org.apache.maven.plugins +@@ -231,6 +274,13 @@ + + + ++ ++ jacoco-report ++ test ++ ++ report ++ ++ + + + +diff --git a/jflex/src/test/java/jflex/core/unicode/CharClassesQuickcheck.java b/jflex/src/test/java/jflex/core/unicode/CharClassesQuickcheck.java +index c31b5221..4744c3ce 100644 +--- a/jflex/src/test/java/jflex/core/unicode/CharClassesQuickcheck.java ++++ b/jflex/src/test/java/jflex/core/unicode/CharClassesQuickcheck.java +@@ -45,7 +45,7 @@ public class CharClassesQuickcheck { + assertThat(c.getMaxCharCode()).isEqualTo(CharClasses.maxChar); + } + +- @Property ++ @Property(trials = 50) + public void addSingle( + CharClasses classes, + @InRange(minInt = 0, maxInt = CharClasses.maxChar) int c1, +@@ -56,7 +56,7 @@ public class CharClassesQuickcheck { + assertThat(classes.getClassCode(c1)).isNotEqualTo(classes.getClassCode(c2)); + } + +- @Property ++ @Property(trials = 50) + public void addSingleSingleton( + CharClasses classes, @InRange(minInt = 0, maxInt = CharClasses.maxChar) int c) { + classes.makeClass(c, false); +@@ -64,7 +64,7 @@ public class CharClassesQuickcheck { + assertThat(set).isEqualTo(IntCharSet.ofCharacter(c)); + } + +- @Property ++ @Property(trials = 50) + public void addSet( + CharClasses classes, + @InRange(maxInt = CharClasses.maxChar) IntCharSet set, +@@ -110,7 +110,7 @@ public class CharClassesQuickcheck { + assertThat(others).isEqualTo(IntCharSet.complementOf(set)); + } + +- @Property ++ @Property(trials = 50) + public void addString( + CharClasses classes, String s, @InRange(minInt = 0, maxInt = CharClasses.maxChar) int c) { + +diff --git a/jflex/src/test/java/jflex/state/StateSetQuickcheck.java b/jflex/src/test/java/jflex/state/StateSetQuickcheck.java +index c3ac7e67..08945ece 100644 +--- a/jflex/src/test/java/jflex/state/StateSetQuickcheck.java ++++ b/jflex/src/test/java/jflex/state/StateSetQuickcheck.java +@@ -154,7 +154,7 @@ public class StateSetQuickcheck { + assertThat(s.hasElement(e)).isFalse(); + } + +- @Property ++ @Property(trials = 50) + public void removeAdd( + @Size(max = 90) @InRange(minInt = 0, maxInt = 100) StateSet s, + @InRange(minInt = 0, maxInt = 100) int e) { +@@ -180,7 +180,7 @@ public class StateSetQuickcheck { + assertThat(set.hasElement(e)).isTrue(); + } + +- @Property ++ @Property(trials = 50) + public void addStateDoesNotRemove(StateSet set, @InRange(minInt = 0, maxInt = 2 ^ 32) int e) { + StateSet setPre = new StateSet(set); + set.addState(e); +@@ -224,7 +224,7 @@ public class StateSetQuickcheck { + assertThat(union1).isEqualTo(union0); + } + +- @Property ++ @Property(trials = 50) + public void containsElements(StateSet s, @InRange(minInt = 0, maxInt = 2 ^ 32) int e) { + s.addState(e); + assertThat(s.containsElements()).isTrue(); diff --git a/artifacts/configs/jflex-50/jflex-50.yaml b/artifacts/configs/jflex-50/jflex-50.yaml new file mode 100644 index 00000000..71819180 --- /dev/null +++ b/artifacts/configs/jflex-50/jflex-50.yaml @@ -0,0 +1,100 @@ +name: jflex-50 +URL: https://github.com/jflex-de/jflex.git +checkoutID: e6d1752bd48a7ccb2a2b78479dc5a73ac475bbb9 +patchName: artifacts/configs/jflex-50/jflex-50.patch +subProject: jflex +mainJar: jflex-1.8.2-jar-with-dependencies.jar +testJar: jflex-1.8.2-tests.jar +#mvnOptions: -DfailIfNoTests=false -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +mvnOptions: -DfailIfNoTests=false +properties: + - name: StateSetQuickcheck#removeAdd + entryPoint: "jflex.state.StateSetQuickcheck.removeAdd(Ljflex/state/StateSet;I)V" + - name: StateSetQuickcheck#addStateDoesNotRemove + entryPoint: "jflex.state.StateSetQuickcheck.addStateDoesNotRemove(Ljflex/state/StateSet;I)V" + - name: StateSetQuickcheck#containsElements + entryPoint: "jflex.state.StateSetQuickcheck.containsElements(Ljflex/state/StateSet;I)V" + - name: CharClassesQuickcheck#addSingle + entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSingle(Ljflex/core/unicode/CharClasses;II)V" + - name: CharClassesQuickcheck#addSingleSingleton + entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSingleSingleton(Ljflex/core/unicode/CharClasses;I)V" + - name: CharClassesQuickcheck#addSet + entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSet(Ljflex/core/unicode/CharClasses;Ljflex/core/unicode/IntCharSet;I)V" + - name: CharClassesQuickcheck#addString + entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addString(Ljflex/core/unicode/CharClasses;Ljava/lang/String;I)V" +# - name: StateSetQuickcheck#size2nbits +# entryPoint: "jflex.state.StateSetQuickcheck.size2nbits(I)V" +# - name: StateSetQuickcheck#containsIsSubset +# entryPoint: "jflex.state.StateSetQuickcheck.containsIsSubset(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addIsUnion +# entryPoint: "jflex.state.StateSetQuickcheck.addIsUnion(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addCommutes +# entryPoint: "jflex.state.StateSetQuickcheck.addCommutes(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addEmpty +# entryPoint: "jflex.state.StateSetQuickcheck.addEmpty(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addSelf +# entryPoint: "jflex.state.StateSetQuickcheck.addSelf(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addIdemPotent +# entryPoint: "jflex.state.StateSetQuickcheck.addIdemPotent(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersect +# entryPoint: "jflex.state.StateSetQuickcheck.intersect(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersectUnchanged +# entryPoint: "jflex.state.StateSetQuickcheck.intersectUnchanged(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersectCommutes +# entryPoint: "jflex.state.StateSetQuickcheck.intersectCommutes(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersectEmpty +# entryPoint: "jflex.state.StateSetQuickcheck.intersectEmpty(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersectSelf +# entryPoint: "jflex.state.StateSetQuickcheck.intersectSelf(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#containsItsElements +# entryPoint: "jflex.state.StateSetQuickcheck.containsItsElements(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#removeRemoves +# entryPoint: "jflex.state.StateSetQuickcheck.removeRemoves(Ljflex/state/StateSet;I)V" +# - name: StateSetQuickcheck#clearMakesEmpty +# entryPoint: "jflex.state.StateSetQuickcheck.clearMakesEmpty(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addStateAdds +# entryPoint: "jflex.state.StateSetQuickcheck.addStateAdds(Ljflex/state/StateSet;I)V" +# - name: StateSetQuickcheck#addStateAdd +# entryPoint: "jflex.state.StateSetQuickcheck.addStateAdd(Ljflex/state/StateSet;I)V" +# - name: StateSetQuickcheck#complementNoOriginalElements +# entryPoint: "jflex.state.StateSetQuickcheck.complementNoOriginalElements(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#complementElements +# entryPoint: "jflex.state.StateSetQuickcheck.complementElements(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#complementUnion +# entryPoint: "jflex.state.StateSetQuickcheck.complementUnion(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#containsNoElements +# entryPoint: "jflex.state.StateSetQuickcheck.containsNoElements(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#copy +# entryPoint: "jflex.state.StateSetQuickcheck.copy(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#copyInto +# entryPoint: "jflex.state.StateSetQuickcheck.copyInto(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#hashCode +# entryPoint: "jflex.state.StateSetQuickcheck.hashCode(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#getAndRemoveRemoves +# entryPoint: "jflex.state.StateSetQuickcheck.getAndRemoveRemoves(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#getAndRemoveIsElement +# entryPoint: "jflex.state.StateSetQuickcheck.getAndRemoveIsElement(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#getAndRemoveAdd +# entryPoint: "jflex.state.StateSetQuickcheck.getAndRemoveAdd(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#enumerator +# entryPoint: "jflex.state.StateSetQuickcheck.enumerator(Ljflex/state/StateSet;)V" +# - name: CharClassesQuickcheck#invariants +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.invariants(Ljflex/core/unicode/CharClasses;)V" +# - name: CharClassesQuickcheck#maxCharCode +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.maxCharCode(Ljflex/core/unicode/CharClasses;)V" +# - name: CharClassesQuickcheck#addSetParts +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSetParts(Ljflex/core/unicode/CharClasses;Ljflex/core/unicode/IntCharSet;)V" +# - name: CharClassesQuickcheck#addSetComplement +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSetComplement(Ljflex/core/unicode/CharClasses;Ljflex/core/unicode/IntCharSet;)V" +# - name: CharClassesQuickcheck#normaliseSingle +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.normaliseSingle(Ljflex/core/unicode/CharClasses;I)V" +# - name: CharClassesQuickcheck#computeTablesEq +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.computeTablesEq(Ljflex/core/unicode/CharClasses;Ljava/util/ArrayList;)V" +# - name: CharClassesQuickcheck#getTablesEq +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.getTablesEq(Ljflex/core/unicode/CharClasses;Ljava/util/ArrayList;)V" +# - name: CharClassesQuickcheck#classCodesUnion +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.classCodesUnion(Ljflex/core/unicode/CharClasses;)V" +# - name: CharClassesQuickcheck#classCodesCode +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.classCodesCode(Ljflex/core/unicode/CharClasses;)V" +# - name: CharClassesQuickcheck#classCodesDisjointOrdered +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.classCodesDisjointOrdered(Ljflex/core/unicode/CharClasses;)V" diff --git a/artifacts/configs/jflex-500/jflex-500.patch b/artifacts/configs/jflex-500/jflex-500.patch new file mode 100644 index 00000000..d7213d3f --- /dev/null +++ b/artifacts/configs/jflex-500/jflex-500.patch @@ -0,0 +1,146 @@ +diff --git a/jflex/pom.xml b/jflex/pom.xml +index 47904b61..185a3790 100644 +--- a/jflex/pom.xml ++++ b/jflex/pom.xml +@@ -51,6 +51,21 @@ + +1 + + ++ ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ ++ ++ ++ report ++ ++ ++ ++ ++ ++ + + + com.github.vbmacher +@@ -156,6 +171,34 @@ + + + ++ ++ ++ ++ test-jar ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-assembly-plugin ++ 3.3.0 ++ ++ ++ ++ jar-with-dependencies ++ ++ ++ ++ ++ ++ make-assembly ++ package ++ ++ single ++ ++ ++ + + + org.apache.maven.plugins +@@ -231,6 +274,13 @@ + + + ++ ++ jacoco-report ++ test ++ ++ report ++ ++ + + + +diff --git a/jflex/src/test/java/jflex/core/unicode/CharClassesQuickcheck.java b/jflex/src/test/java/jflex/core/unicode/CharClassesQuickcheck.java +index c31b5221..4744c3ce 100644 +--- a/jflex/src/test/java/jflex/core/unicode/CharClassesQuickcheck.java ++++ b/jflex/src/test/java/jflex/core/unicode/CharClassesQuickcheck.java +@@ -45,7 +45,7 @@ public class CharClassesQuickcheck { + assertThat(c.getMaxCharCode()).isEqualTo(CharClasses.maxChar); + } + +- @Property ++ @Property(trials = 500) + public void addSingle( + CharClasses classes, + @InRange(minInt = 0, maxInt = CharClasses.maxChar) int c1, +@@ -56,7 +56,7 @@ public class CharClassesQuickcheck { + assertThat(classes.getClassCode(c1)).isNotEqualTo(classes.getClassCode(c2)); + } + +- @Property ++ @Property(trials = 500) + public void addSingleSingleton( + CharClasses classes, @InRange(minInt = 0, maxInt = CharClasses.maxChar) int c) { + classes.makeClass(c, false); +@@ -64,7 +64,7 @@ public class CharClassesQuickcheck { + assertThat(set).isEqualTo(IntCharSet.ofCharacter(c)); + } + +- @Property ++ @Property(trials = 500) + public void addSet( + CharClasses classes, + @InRange(maxInt = CharClasses.maxChar) IntCharSet set, +@@ -110,7 +110,7 @@ public class CharClassesQuickcheck { + assertThat(others).isEqualTo(IntCharSet.complementOf(set)); + } + +- @Property ++ @Property(trials = 500) + public void addString( + CharClasses classes, String s, @InRange(minInt = 0, maxInt = CharClasses.maxChar) int c) { + +diff --git a/jflex/src/test/java/jflex/state/StateSetQuickcheck.java b/jflex/src/test/java/jflex/state/StateSetQuickcheck.java +index c3ac7e67..08945ece 100644 +--- a/jflex/src/test/java/jflex/state/StateSetQuickcheck.java ++++ b/jflex/src/test/java/jflex/state/StateSetQuickcheck.java +@@ -154,7 +154,7 @@ public class StateSetQuickcheck { + assertThat(s.hasElement(e)).isFalse(); + } + +- @Property ++ @Property(trials = 500) + public void removeAdd( + @Size(max = 90) @InRange(minInt = 0, maxInt = 100) StateSet s, + @InRange(minInt = 0, maxInt = 100) int e) { +@@ -180,7 +180,7 @@ public class StateSetQuickcheck { + assertThat(set.hasElement(e)).isTrue(); + } + +- @Property ++ @Property(trials = 500) + public void addStateDoesNotRemove(StateSet set, @InRange(minInt = 0, maxInt = 2 ^ 32) int e) { + StateSet setPre = new StateSet(set); + set.addState(e); +@@ -224,7 +224,7 @@ public class StateSetQuickcheck { + assertThat(union1).isEqualTo(union0); + } + +- @Property ++ @Property(trials = 500) + public void containsElements(StateSet s, @InRange(minInt = 0, maxInt = 2 ^ 32) int e) { + s.addState(e); + assertThat(s.containsElements()).isTrue(); diff --git a/artifacts/configs/jflex-500/jflex-500.yaml b/artifacts/configs/jflex-500/jflex-500.yaml new file mode 100644 index 00000000..741c8a3d --- /dev/null +++ b/artifacts/configs/jflex-500/jflex-500.yaml @@ -0,0 +1,100 @@ +name: jflex-500 +URL: https://github.com/jflex-de/jflex.git +checkoutID: e6d1752bd48a7ccb2a2b78479dc5a73ac475bbb9 +patchName: artifacts/configs/jflex-500/jflex-500.patch +subProject: jflex +mainJar: jflex-1.8.2-jar-with-dependencies.jar +testJar: jflex-1.8.2-tests.jar +#mvnOptions: -DfailIfNoTests=false -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +mvnOptions: -DfailIfNoTests=false +properties: + - name: StateSetQuickcheck#removeAdd + entryPoint: "jflex.state.StateSetQuickcheck.removeAdd(Ljflex/state/StateSet;I)V" + - name: StateSetQuickcheck#addStateDoesNotRemove + entryPoint: "jflex.state.StateSetQuickcheck.addStateDoesNotRemove(Ljflex/state/StateSet;I)V" + - name: StateSetQuickcheck#containsElements + entryPoint: "jflex.state.StateSetQuickcheck.containsElements(Ljflex/state/StateSet;I)V" + - name: CharClassesQuickcheck#addSingle + entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSingle(Ljflex/core/unicode/CharClasses;II)V" + - name: CharClassesQuickcheck#addSingleSingleton + entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSingleSingleton(Ljflex/core/unicode/CharClasses;I)V" + - name: CharClassesQuickcheck#addSet + entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSet(Ljflex/core/unicode/CharClasses;Ljflex/core/unicode/IntCharSet;I)V" + - name: CharClassesQuickcheck#addString + entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addString(Ljflex/core/unicode/CharClasses;Ljava/lang/String;I)V" +# - name: StateSetQuickcheck#size2nbits +# entryPoint: "jflex.state.StateSetQuickcheck.size2nbits(I)V" +# - name: StateSetQuickcheck#containsIsSubset +# entryPoint: "jflex.state.StateSetQuickcheck.containsIsSubset(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addIsUnion +# entryPoint: "jflex.state.StateSetQuickcheck.addIsUnion(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addCommutes +# entryPoint: "jflex.state.StateSetQuickcheck.addCommutes(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addEmpty +# entryPoint: "jflex.state.StateSetQuickcheck.addEmpty(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addSelf +# entryPoint: "jflex.state.StateSetQuickcheck.addSelf(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addIdemPotent +# entryPoint: "jflex.state.StateSetQuickcheck.addIdemPotent(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersect +# entryPoint: "jflex.state.StateSetQuickcheck.intersect(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersectUnchanged +# entryPoint: "jflex.state.StateSetQuickcheck.intersectUnchanged(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersectCommutes +# entryPoint: "jflex.state.StateSetQuickcheck.intersectCommutes(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersectEmpty +# entryPoint: "jflex.state.StateSetQuickcheck.intersectEmpty(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersectSelf +# entryPoint: "jflex.state.StateSetQuickcheck.intersectSelf(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#containsItsElements +# entryPoint: "jflex.state.StateSetQuickcheck.containsItsElements(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#removeRemoves +# entryPoint: "jflex.state.StateSetQuickcheck.removeRemoves(Ljflex/state/StateSet;I)V" +# - name: StateSetQuickcheck#clearMakesEmpty +# entryPoint: "jflex.state.StateSetQuickcheck.clearMakesEmpty(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addStateAdds +# entryPoint: "jflex.state.StateSetQuickcheck.addStateAdds(Ljflex/state/StateSet;I)V" +# - name: StateSetQuickcheck#addStateAdd +# entryPoint: "jflex.state.StateSetQuickcheck.addStateAdd(Ljflex/state/StateSet;I)V" +# - name: StateSetQuickcheck#complementNoOriginalElements +# entryPoint: "jflex.state.StateSetQuickcheck.complementNoOriginalElements(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#complementElements +# entryPoint: "jflex.state.StateSetQuickcheck.complementElements(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#complementUnion +# entryPoint: "jflex.state.StateSetQuickcheck.complementUnion(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#containsNoElements +# entryPoint: "jflex.state.StateSetQuickcheck.containsNoElements(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#copy +# entryPoint: "jflex.state.StateSetQuickcheck.copy(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#copyInto +# entryPoint: "jflex.state.StateSetQuickcheck.copyInto(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#hashCode +# entryPoint: "jflex.state.StateSetQuickcheck.hashCode(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#getAndRemoveRemoves +# entryPoint: "jflex.state.StateSetQuickcheck.getAndRemoveRemoves(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#getAndRemoveIsElement +# entryPoint: "jflex.state.StateSetQuickcheck.getAndRemoveIsElement(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#getAndRemoveAdd +# entryPoint: "jflex.state.StateSetQuickcheck.getAndRemoveAdd(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#enumerator +# entryPoint: "jflex.state.StateSetQuickcheck.enumerator(Ljflex/state/StateSet;)V" +# - name: CharClassesQuickcheck#invariants +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.invariants(Ljflex/core/unicode/CharClasses;)V" +# - name: CharClassesQuickcheck#maxCharCode +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.maxCharCode(Ljflex/core/unicode/CharClasses;)V" +# - name: CharClassesQuickcheck#addSetParts +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSetParts(Ljflex/core/unicode/CharClasses;Ljflex/core/unicode/IntCharSet;)V" +# - name: CharClassesQuickcheck#addSetComplement +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSetComplement(Ljflex/core/unicode/CharClasses;Ljflex/core/unicode/IntCharSet;)V" +# - name: CharClassesQuickcheck#normaliseSingle +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.normaliseSingle(Ljflex/core/unicode/CharClasses;I)V" +# - name: CharClassesQuickcheck#computeTablesEq +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.computeTablesEq(Ljflex/core/unicode/CharClasses;Ljava/util/ArrayList;)V" +# - name: CharClassesQuickcheck#getTablesEq +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.getTablesEq(Ljflex/core/unicode/CharClasses;Ljava/util/ArrayList;)V" +# - name: CharClassesQuickcheck#classCodesUnion +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.classCodesUnion(Ljflex/core/unicode/CharClasses;)V" +# - name: CharClassesQuickcheck#classCodesCode +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.classCodesCode(Ljflex/core/unicode/CharClasses;)V" +# - name: CharClassesQuickcheck#classCodesDisjointOrdered +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.classCodesDisjointOrdered(Ljflex/core/unicode/CharClasses;)V" diff --git a/artifacts/configs/jflex-fixed/jflex-fixed.patch b/artifacts/configs/jflex-fixed/jflex-fixed.patch new file mode 100644 index 00000000..c15d5b50 --- /dev/null +++ b/artifacts/configs/jflex-fixed/jflex-fixed.patch @@ -0,0 +1,436 @@ +diff --git a/jflex/pom.xml b/jflex/pom.xml +index 47904b61..185a3790 100644 +--- a/jflex/pom.xml ++++ b/jflex/pom.xml +@@ -51,6 +51,21 @@ + +1 + + ++ ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ ++ ++ ++ report ++ ++ ++ ++ ++ ++ + + + com.github.vbmacher +@@ -156,6 +171,34 @@ + + + ++ ++ ++ ++ test-jar ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-assembly-plugin ++ 3.3.0 ++ ++ ++ ++ jar-with-dependencies ++ ++ ++ ++ ++ ++ make-assembly ++ package ++ ++ single ++ ++ ++ + + + org.apache.maven.plugins +@@ -231,6 +274,13 @@ + + + ++ ++ jacoco-report ++ test ++ ++ report ++ ++ + + + +diff --git a/jflex/src/main/java/jflex/state/StateSet.java b/jflex/src/main/java/jflex/state/StateSet.java +index 2efd13f1..8f7ec527 100644 +--- a/jflex/src/main/java/jflex/state/StateSet.java ++++ b/jflex/src/main/java/jflex/state/StateSet.java +@@ -403,4 +403,13 @@ public final class StateSet implements Iterable { + public Iterator iterator() { + return states(); + } ++ ++ /** ++ * Provide the max value that can be stored without a resize ++ * ++ * @return an int of the max value ++ */ ++ public int getCurrentMaxState() { ++ return (bits.length << BITS) | ~(0xFFFFFFFF << BITS); ++ } + } +diff --git a/jflex/src/test/java/jflex/core/unicode/BUILD.bazel b/jflex/src/test/java/jflex/core/unicode/BUILD.bazel +index b7ac9de7..9bada3a8 100644 +--- a/jflex/src/test/java/jflex/core/unicode/BUILD.bazel ++++ b/jflex/src/test/java/jflex/core/unicode/BUILD.bazel +@@ -45,3 +45,20 @@ java_test( + "//third_party/com/google/truth", + ], + ) ++ ++java_library( ++ name = "IntCharSetGen", ++ testonly = True, ++ srcs = [ ++ "IntCharGen.java", ++ "IntCharSetGen.java", ++ ], ++ deps = [ ++ "//jflex/src/main/java/jflex/chars", ++ "//jflex/src/main/java/jflex/core/unicode", ++ "//jflex/src/main/java/jflex/logging", ++ "//jflex/src/test/java/jflex/chars", ++ "//third_party/com/google/truth", ++ "//third_party/com/pholser/quickcheck", ++ ], ++) +diff --git a/jflex/src/test/java/jflex/core/unicode/CharClassesQuickcheck.java b/jflex/src/test/java/jflex/core/unicode/CharClassesQuickcheck.java +index c31b5221..357faf7e 100644 +--- a/jflex/src/test/java/jflex/core/unicode/CharClassesQuickcheck.java ++++ b/jflex/src/test/java/jflex/core/unicode/CharClassesQuickcheck.java +@@ -12,6 +12,7 @@ package jflex.core.unicode; + import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assume.assumeTrue; + ++import com.pholser.junit.quickcheck.From; + import com.pholser.junit.quickcheck.Property; + import com.pholser.junit.quickcheck.generator.InRange; + import com.pholser.junit.quickcheck.generator.Size; +@@ -47,9 +48,7 @@ public class CharClassesQuickcheck { + + @Property + public void addSingle( +- CharClasses classes, +- @InRange(minInt = 0, maxInt = CharClasses.maxChar) int c1, +- @InRange(minInt = 0, maxInt = CharClasses.maxChar) int c2) { ++ CharClasses classes, @From(IntCharGen.class) int c1, @From(IntCharGen.class) int c2) { + assumeTrue(c1 != c2); + classes.makeClass(c1, false); + assertThat(classes.invariants()).isTrue(); +@@ -57,8 +56,7 @@ public class CharClassesQuickcheck { + } + + @Property +- public void addSingleSingleton( +- CharClasses classes, @InRange(minInt = 0, maxInt = CharClasses.maxChar) int c) { ++ public void addSingleSingleton(CharClasses classes, @From(IntCharGen.class) int c) { + classes.makeClass(c, false); + IntCharSet set = classes.getCharClass(classes.getClassCode(c)); + assertThat(set).isEqualTo(IntCharSet.ofCharacter(c)); +@@ -68,7 +66,7 @@ public class CharClassesQuickcheck { + public void addSet( + CharClasses classes, + @InRange(maxInt = CharClasses.maxChar) IntCharSet set, +- @InRange(minInt = 0, maxInt = CharClasses.maxChar) int c) { ++ @From(IntCharGen.class) int c) { + + assumeTrue(!set.contains(c)); + +@@ -111,8 +109,7 @@ public class CharClassesQuickcheck { + } + + @Property +- public void addString( +- CharClasses classes, String s, @InRange(minInt = 0, maxInt = CharClasses.maxChar) int c) { ++ public void addString(CharClasses classes, String s, @From(IntCharGen.class) int c) { + + assumeTrue(s.indexOf(c) < 0); + +diff --git a/jflex/src/test/java/jflex/core/unicode/IntCharGen.java b/jflex/src/test/java/jflex/core/unicode/IntCharGen.java +new file mode 100644 +index 00000000..45e47ee8 +--- /dev/null ++++ b/jflex/src/test/java/jflex/core/unicode/IntCharGen.java +@@ -0,0 +1,47 @@ ++package jflex.core.unicode; ++ ++import com.pholser.junit.quickcheck.generator.GenerationStatus; ++import com.pholser.junit.quickcheck.generator.Generator; ++import com.pholser.junit.quickcheck.random.SourceOfRandomness; ++import java.util.List; ++import jflex.chars.Interval; ++import jflex.logging.Out; ++ ++/** ++ * Generator for random Integer values that ensure to sometimes generate a cased character ++ * ++ * @author Jesse Coultas ++ * @version JFlex 1.8.2 ++ */ ++public class IntCharGen extends Generator { ++ /** Constructs generator for CharClasses */ ++ public IntCharGen() throws UnicodeProperties.UnsupportedUnicodeVersionException { ++ super(Integer.class); ++ } ++ ++ @Override ++ public Integer generate(SourceOfRandomness r, GenerationStatus status) { ++ // ensure we sometimes generate an int that has case options ++ if (r.nextBoolean()) { ++ try { ++ return getRandomCased(r); ++ } catch (UnicodeProperties.UnsupportedUnicodeVersionException e) { ++ Out.warning("Unable to fetch a random cased value - " + e.getMessage()); ++ } ++ } ++ ++ return r.nextInt(0, CharClasses.maxChar); ++ } ++ ++ public static Integer getRandomCased(SourceOfRandomness r) ++ throws UnicodeProperties.UnsupportedUnicodeVersionException { ++ // get list of casedIntervals ++ List casedIntervals = (new UnicodeProperties()).getIntCharSet("cased").getIntervals(); ++ ++ // randomly pick an interval ++ Interval interval = casedIntervals.get(r.nextInt(0, casedIntervals.size() - 1)); ++ ++ // return a value between start and end of interval ++ return r.nextInt(interval.start, interval.end); ++ } ++} +diff --git a/jflex/src/test/java/jflex/core/unicode/IntCharSetGen.java b/jflex/src/test/java/jflex/core/unicode/IntCharSetGen.java +index bd480a22..90ea80eb 100644 +--- a/jflex/src/test/java/jflex/core/unicode/IntCharSetGen.java ++++ b/jflex/src/test/java/jflex/core/unicode/IntCharSetGen.java +@@ -15,6 +15,7 @@ import com.pholser.junit.quickcheck.generator.InRange; + import com.pholser.junit.quickcheck.generator.Size; + import com.pholser.junit.quickcheck.random.SourceOfRandomness; + import jflex.chars.IntervalGen; ++import jflex.logging.Out; + + /** + * Generator for random {@link IntCharSet} instances. +@@ -48,6 +49,15 @@ public class IntCharSetGen extends Generator { + result.add(intervals.generate(r, status)); + } + ++ // randomly add possible additional cased character ++ if (numIntervals < maxSize && r.nextBoolean()) { ++ try { ++ result.add(IntCharGen.getRandomCased(r)); ++ } catch (UnicodeProperties.UnsupportedUnicodeVersionException e) { ++ Out.warning("Unable to fetch a random cased value - " + e.getMessage()); ++ } ++ } ++ + return result; + } + +diff --git a/jflex/src/test/java/jflex/state/BUILD.bazel b/jflex/src/test/java/jflex/state/BUILD.bazel +index cbfc093a..89aaee76 100644 +--- a/jflex/src/test/java/jflex/state/BUILD.bazel ++++ b/jflex/src/test/java/jflex/state/BUILD.bazel +@@ -2,6 +2,7 @@ java_test( + name = "StateSetQuickcheck", + timeout = "short", + srcs = [ ++ "OffsetGen.java", + "StateSetGen.java", + "StateSetQuickcheck.java", + ], +diff --git a/jflex/src/test/java/jflex/state/OffsetGen.java b/jflex/src/test/java/jflex/state/OffsetGen.java +new file mode 100644 +index 00000000..fd9d5ab3 +--- /dev/null ++++ b/jflex/src/test/java/jflex/state/OffsetGen.java +@@ -0,0 +1,45 @@ ++package jflex.state; ++ ++import com.pholser.junit.quickcheck.generator.GenerationStatus; ++import com.pholser.junit.quickcheck.generator.Generator; ++import com.pholser.junit.quickcheck.random.SourceOfRandomness; ++ ++/** Generator for Offset data values */ ++public class OffsetGen extends Generator { ++ public OffsetGen() { ++ super(Integer.class); ++ } ++ ++ @Override ++ public Integer generate(SourceOfRandomness r, GenerationStatus status) { ++ int rnd = r.nextInt(1, 100); ++ ++ // 5% change of getting number 0 ++ if (rnd >= 1 && rnd <= 5) { ++ return 0; ++ } ++ ++ // 5% change of getting number 1 ++ if (rnd >= 6 && rnd <= 10) { ++ return 1; ++ } ++ ++ // 5% change of getting Integer.MAX_VALUE ++ if (rnd >= 11 && rnd <= 15) { ++ return Integer.MAX_VALUE; ++ } ++ ++ // 15% chance of getting a "larger" size ++ if (rnd >= 16 && rnd <= 30) { ++ return r.nextInt(200_001, 10_000_000); ++ } ++ ++ // 5% chance of getting a "huge" size ++ if (rnd >= 31 && rnd <= 35) { ++ return r.nextInt(10_000_001, Integer.MAX_VALUE); ++ } ++ ++ // 77% - normalish size ++ return r.nextInt(100, 20_000); ++ } ++} +diff --git a/jflex/src/test/java/jflex/state/StateSetGen.java b/jflex/src/test/java/jflex/state/StateSetGen.java +index 57e55e30..ab287952 100644 +--- a/jflex/src/test/java/jflex/state/StateSetGen.java ++++ b/jflex/src/test/java/jflex/state/StateSetGen.java +@@ -50,6 +50,11 @@ public class StateSetGen extends Generator { + result.addState(r.nextInt(minRange, maxRange)); + } + ++ // add large value 20% of the time ++ if (r.nextInt(1, 5) == 5) { ++ result.addState(r.nextInt(minRange + 100_000, maxRange + 100_000)); ++ } ++ + return result; + } + +diff --git a/jflex/src/test/java/jflex/state/StateSetQuickcheck.java b/jflex/src/test/java/jflex/state/StateSetQuickcheck.java +index c3ac7e67..81c56afb 100644 +--- a/jflex/src/test/java/jflex/state/StateSetQuickcheck.java ++++ b/jflex/src/test/java/jflex/state/StateSetQuickcheck.java +@@ -11,9 +11,13 @@ package jflex.state; + + import static com.google.common.truth.Truth.assertThat; + import static com.google.common.truth.Truth.assertWithMessage; ++import static org.hamcrest.core.IsEqual.equalTo; ++import static org.junit.Assume.assumeThat; + import static org.junit.Assume.assumeTrue; + ++import com.pholser.junit.quickcheck.From; + import com.pholser.junit.quickcheck.Property; ++import com.pholser.junit.quickcheck.generator.Also; + import com.pholser.junit.quickcheck.generator.InRange; + import com.pholser.junit.quickcheck.generator.Size; + import com.pholser.junit.quickcheck.runner.JUnitQuickcheck; +@@ -167,6 +171,32 @@ public class StateSetQuickcheck { + assertThat(s).isEqualTo(sPre); + } + ++ @Property ++ public void removeAddResize( ++ @Size(max = 90) @InRange(minInt = 0, maxInt = 100) StateSet s, ++ @InRange(minInt = 0, maxInt = 100) int e, ++ @From(OffsetGen.class) int largeOffset) { ++ assumeTrue(s.hasElement(e)); ++ StateSet sPre = new StateSet(s); ++ s.remove(e); ++ assertThat(sPre.contains(s)).isTrue(); ++ assertThat(s).isNotEqualTo(sPre); ++ s.addState(e); ++ assertThat(s).isEqualTo(sPre); ++ ++ // add larger state value to force resize ++ int largerState; ++ try { ++ largerState = Math.addExact(s.getCurrentMaxState(), largeOffset); ++ } catch (ArithmeticException arithmeticException) { ++ largerState = Integer.MAX_VALUE; ++ } ++ s.addState(largerState); ++ assertThat(s).contains(largerState); ++ s.remove(largerState); ++ assertThat(s).isEqualTo(sPre); ++ } ++ + @Property + public void clearMakesEmpty(StateSet set) { + set.clear(); +@@ -181,10 +211,27 @@ public class StateSetQuickcheck { + } + + @Property +- public void addStateDoesNotRemove(StateSet set, @InRange(minInt = 0, maxInt = 2 ^ 32) int e) { ++ public void addStateDoesNotRemove( ++ StateSet set, @Also("2147483647") @InRange(minInt = 0, maxInt = 34) int e) { + StateSet setPre = new StateSet(set); + set.addState(e); + assertThat(set.contains(setPre)).isTrue(); ++ assertThat(set.hasElement(e)).isTrue(); ++ ++ // add an out of range value to increase coverage of contains ++ ++ // offset to StateSetGen.maxRange + 1 ++ int offset = ++ 1001; // note this effected by InRange, so this needs to be adjusted based on annotations ++ // on set, default is set ++ ++ // if no overflow then offset + e, else overflow so use MAX_VALUE ++ int newValue = (Integer.MAX_VALUE - offset) >= e ? offset + e : Integer.MAX_VALUE; ++ assumeThat(set.hasElement(newValue), equalTo(false)); ++ set.addState(newValue); ++ assertThat(set.contains(setPre)).isTrue(); ++ assertThat(set.hasElement(newValue)).isTrue(); ++ assertThat(setPre.contains(set)).isFalse(); + } + + @Property +@@ -208,6 +255,11 @@ public class StateSetQuickcheck { + StateSet comp = s1.complement(s2); + // only elements of s2 are in the complement + assertThat(s2.contains(comp)).isTrue(); ++ ++ // ensure that comp does not contain s1 ++ if (s1.containsElements()) { // if s1 is {}, then it will always be contained in comp ++ assertThat(comp.contains(s1)).isFalse(); ++ } + } + + @Property +@@ -228,6 +280,13 @@ public class StateSetQuickcheck { + public void containsElements(StateSet s, @InRange(minInt = 0, maxInt = 2 ^ 32) int e) { + s.addState(e); + assertThat(s.containsElements()).isTrue(); ++ ++ // remove each added element, ot ensure containsElements continues to work as elements are ++ // removed ++ while (s.containsElements()) { ++ s.getAndRemoveElement(); ++ } ++ assertThat(s.containsElements()).isFalse(); + } + + @Property diff --git a/artifacts/configs/jflex-fixed/jflex-fixed.yaml b/artifacts/configs/jflex-fixed/jflex-fixed.yaml new file mode 100644 index 00000000..03e9d9a1 --- /dev/null +++ b/artifacts/configs/jflex-fixed/jflex-fixed.yaml @@ -0,0 +1,104 @@ +name: jflex-fixed +URL: https://github.com/jflex-de/jflex.git +checkoutID: e6d1752bd48a7ccb2a2b78479dc5a73ac475bbb9 +patchName: artifacts/configs/jflex-fixed/jflex-fixed.patch +subProject: jflex +mainJar: jflex-1.8.2-jar-with-dependencies.jar +testJar: jflex-1.8.2-tests.jar +#mvnOptions: -DfailIfNoTests=false -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +mvnOptions: -DfailIfNoTests=false +properties: + - name: StateSetQuickcheck#removeAddResize + entryPoint: "jflex.state.StateSetQuickcheck.removeAddResize(Ljflex/state/StateSet;II)V" + - name: StateSetQuickcheck#removeAdd + entryPoint: "jflex.state.StateSetQuickcheck.removeAdd(Ljflex/state/StateSet;I)V" + - name: StateSetQuickcheck#addStateDoesNotRemove + entryPoint: "jflex.state.StateSetQuickcheck.addStateDoesNotRemove(Ljflex/state/StateSet;I)V" + - name: StateSetQuickcheck#containsElements + entryPoint: "jflex.state.StateSetQuickcheck.containsElements(Ljflex/state/StateSet;I)V" + - name: CharClassesQuickcheck#addSingle + entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSingle(Ljflex/core/unicode/CharClasses;II)V" + - name: CharClassesQuickcheck#addSingleSingleton + entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSingleSingleton(Ljflex/core/unicode/CharClasses;I)V" + - name: CharClassesQuickcheck#addSet + entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSet(Ljflex/core/unicode/CharClasses;Ljflex/core/unicode/IntCharSet;I)V" + - name: CharClassesQuickcheck#addString + entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addString(Ljflex/core/unicode/CharClasses;Ljava/lang/String;I)V" + +# - name: StateSetQuickcheck#size2nbits +# entryPoint: "jflex.state.StateSetQuickcheck.size2nbits(I)V" +# - name: StateSetQuickcheck#containsIsSubset +# entryPoint: "jflex.state.StateSetQuickcheck.containsIsSubset(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addIsUnion +# entryPoint: "jflex.state.StateSetQuickcheck.addIsUnion(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addCommutes +# entryPoint: "jflex.state.StateSetQuickcheck.addCommutes(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addEmpty +# entryPoint: "jflex.state.StateSetQuickcheck.addEmpty(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addSelf +# entryPoint: "jflex.state.StateSetQuickcheck.addSelf(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addIdemPotent +# entryPoint: "jflex.state.StateSetQuickcheck.addIdemPotent(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersect +# entryPoint: "jflex.state.StateSetQuickcheck.intersect(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersectUnchanged +# entryPoint: "jflex.state.StateSetQuickcheck.intersectUnchanged(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersectCommutes +# entryPoint: "jflex.state.StateSetQuickcheck.intersectCommutes(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersectEmpty +# entryPoint: "jflex.state.StateSetQuickcheck.intersectEmpty(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersectSelf +# entryPoint: "jflex.state.StateSetQuickcheck.intersectSelf(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#containsItsElements +# entryPoint: "jflex.state.StateSetQuickcheck.containsItsElements(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#removeRemoves +# entryPoint: "jflex.state.StateSetQuickcheck.removeRemoves(Ljflex/state/StateSet;I)V" +# - name: StateSetQuickcheck#clearMakesEmpty +# entryPoint: "jflex.state.StateSetQuickcheck.clearMakesEmpty(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addStateAdds +# entryPoint: "jflex.state.StateSetQuickcheck.addStateAdds(Ljflex/state/StateSet;I)V" +# - name: StateSetQuickcheck#addStateAdd +# entryPoint: "jflex.state.StateSetQuickcheck.addStateAdd(Ljflex/state/StateSet;I)V" +# - name: StateSetQuickcheck#complementNoOriginalElements +# entryPoint: "jflex.state.StateSetQuickcheck.complementNoOriginalElements(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#complementElements +# entryPoint: "jflex.state.StateSetQuickcheck.complementElements(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#complementUnion +# entryPoint: "jflex.state.StateSetQuickcheck.complementUnion(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#containsNoElements +# entryPoint: "jflex.state.StateSetQuickcheck.containsNoElements(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#copy +# entryPoint: "jflex.state.StateSetQuickcheck.copy(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#copyInto +# entryPoint: "jflex.state.StateSetQuickcheck.copyInto(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#hashCode +# entryPoint: "jflex.state.StateSetQuickcheck.hashCode(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#getAndRemoveRemoves +# entryPoint: "jflex.state.StateSetQuickcheck.getAndRemoveRemoves(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#getAndRemoveIsElement +# entryPoint: "jflex.state.StateSetQuickcheck.getAndRemoveIsElement(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#getAndRemoveAdd +# entryPoint: "jflex.state.StateSetQuickcheck.getAndRemoveAdd(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#enumerator +# entryPoint: "jflex.state.StateSetQuickcheck.enumerator(Ljflex/state/StateSet;)V" +# - name: CharClassesQuickcheck#invariants +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.invariants(Ljflex/core/unicode/CharClasses;)V" +# - name: CharClassesQuickcheck#maxCharCode +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.maxCharCode(Ljflex/core/unicode/CharClasses;)V" +# - name: CharClassesQuickcheck#addSetParts +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSetParts(Ljflex/core/unicode/CharClasses;Ljflex/core/unicode/IntCharSet;)V" +# - name: CharClassesQuickcheck#addSetComplement +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSetComplement(Ljflex/core/unicode/CharClasses;Ljflex/core/unicode/IntCharSet;)V" +# - name: CharClassesQuickcheck#normaliseSingle +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.normaliseSingle(Ljflex/core/unicode/CharClasses;I)V" +# - name: CharClassesQuickcheck#computeTablesEq +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.computeTablesEq(Ljflex/core/unicode/CharClasses;Ljava/util/ArrayList;)V" +# - name: CharClassesQuickcheck#getTablesEq +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.getTablesEq(Ljflex/core/unicode/CharClasses;Ljava/util/ArrayList;)V" +# - name: CharClassesQuickcheck#classCodesUnion +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.classCodesUnion(Ljflex/core/unicode/CharClasses;)V" +# - name: CharClassesQuickcheck#classCodesCode +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.classCodesCode(Ljflex/core/unicode/CharClasses;)V" +# - name: CharClassesQuickcheck#classCodesDisjointOrdered +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.classCodesDisjointOrdered(Ljflex/core/unicode/CharClasses;)V" + diff --git a/artifacts/configs/jflex/jflex.patch b/artifacts/configs/jflex/jflex.patch new file mode 100644 index 00000000..5048f75b --- /dev/null +++ b/artifacts/configs/jflex/jflex.patch @@ -0,0 +1,75 @@ +diff --git a/jflex/pom.xml b/jflex/pom.xml +index 47904b61..185a3790 100644 +--- a/jflex/pom.xml ++++ b/jflex/pom.xml +@@ -51,6 +51,21 @@ + +1 + + ++ ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ ++ ++ ++ report ++ ++ ++ ++ ++ ++ + + + com.github.vbmacher +@@ -156,6 +171,34 @@ + + + ++ ++ ++ ++ test-jar ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-assembly-plugin ++ 3.3.0 ++ ++ ++ ++ jar-with-dependencies ++ ++ ++ ++ ++ ++ make-assembly ++ package ++ ++ single ++ ++ ++ + + + org.apache.maven.plugins +@@ -231,6 +274,13 @@ + + + ++ ++ jacoco-report ++ test ++ ++ report ++ ++ + + + diff --git a/artifacts/configs/jflex/jflex.yaml b/artifacts/configs/jflex/jflex.yaml new file mode 100644 index 00000000..a26deaf0 --- /dev/null +++ b/artifacts/configs/jflex/jflex.yaml @@ -0,0 +1,100 @@ +name: jflex +URL: https://github.com/jflex-de/jflex.git +checkoutID: e6d1752bd48a7ccb2a2b78479dc5a73ac475bbb9 +patchName: artifacts/configs/jflex/jflex.patch +subProject: jflex +mainJar: jflex-1.8.2-jar-with-dependencies.jar +testJar: jflex-1.8.2-tests.jar +#mvnOptions: -DfailIfNoTests=false -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +mvnOptions: -DfailIfNoTests=false +properties: + - name: StateSetQuickcheck#removeAdd + entryPoint: "jflex.state.StateSetQuickcheck.removeAdd(Ljflex/state/StateSet;I)V" + - name: StateSetQuickcheck#addStateDoesNotRemove + entryPoint: "jflex.state.StateSetQuickcheck.addStateDoesNotRemove(Ljflex/state/StateSet;I)V" + - name: StateSetQuickcheck#containsElements + entryPoint: "jflex.state.StateSetQuickcheck.containsElements(Ljflex/state/StateSet;I)V" + - name: CharClassesQuickcheck#addSingle + entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSingle(Ljflex/core/unicode/CharClasses;II)V" + - name: CharClassesQuickcheck#addSingleSingleton + entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSingleSingleton(Ljflex/core/unicode/CharClasses;I)V" + - name: CharClassesQuickcheck#addSet + entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSet(Ljflex/core/unicode/CharClasses;Ljflex/core/unicode/IntCharSet;I)V" + - name: CharClassesQuickcheck#addString + entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addString(Ljflex/core/unicode/CharClasses;Ljava/lang/String;I)V" +# - name: StateSetQuickcheck#size2nbits +# entryPoint: "jflex.state.StateSetQuickcheck.size2nbits(I)V" +# - name: StateSetQuickcheck#containsIsSubset +# entryPoint: "jflex.state.StateSetQuickcheck.containsIsSubset(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addIsUnion +# entryPoint: "jflex.state.StateSetQuickcheck.addIsUnion(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addCommutes +# entryPoint: "jflex.state.StateSetQuickcheck.addCommutes(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addEmpty +# entryPoint: "jflex.state.StateSetQuickcheck.addEmpty(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addSelf +# entryPoint: "jflex.state.StateSetQuickcheck.addSelf(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addIdemPotent +# entryPoint: "jflex.state.StateSetQuickcheck.addIdemPotent(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersect +# entryPoint: "jflex.state.StateSetQuickcheck.intersect(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersectUnchanged +# entryPoint: "jflex.state.StateSetQuickcheck.intersectUnchanged(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersectCommutes +# entryPoint: "jflex.state.StateSetQuickcheck.intersectCommutes(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersectEmpty +# entryPoint: "jflex.state.StateSetQuickcheck.intersectEmpty(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#intersectSelf +# entryPoint: "jflex.state.StateSetQuickcheck.intersectSelf(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#containsItsElements +# entryPoint: "jflex.state.StateSetQuickcheck.containsItsElements(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#removeRemoves +# entryPoint: "jflex.state.StateSetQuickcheck.removeRemoves(Ljflex/state/StateSet;I)V" +# - name: StateSetQuickcheck#clearMakesEmpty +# entryPoint: "jflex.state.StateSetQuickcheck.clearMakesEmpty(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#addStateAdds +# entryPoint: "jflex.state.StateSetQuickcheck.addStateAdds(Ljflex/state/StateSet;I)V" +# - name: StateSetQuickcheck#addStateAdd +# entryPoint: "jflex.state.StateSetQuickcheck.addStateAdd(Ljflex/state/StateSet;I)V" +# - name: StateSetQuickcheck#complementNoOriginalElements +# entryPoint: "jflex.state.StateSetQuickcheck.complementNoOriginalElements(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#complementElements +# entryPoint: "jflex.state.StateSetQuickcheck.complementElements(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#complementUnion +# entryPoint: "jflex.state.StateSetQuickcheck.complementUnion(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#containsNoElements +# entryPoint: "jflex.state.StateSetQuickcheck.containsNoElements(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#copy +# entryPoint: "jflex.state.StateSetQuickcheck.copy(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#copyInto +# entryPoint: "jflex.state.StateSetQuickcheck.copyInto(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#hashCode +# entryPoint: "jflex.state.StateSetQuickcheck.hashCode(Ljflex/state/StateSet;Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#getAndRemoveRemoves +# entryPoint: "jflex.state.StateSetQuickcheck.getAndRemoveRemoves(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#getAndRemoveIsElement +# entryPoint: "jflex.state.StateSetQuickcheck.getAndRemoveIsElement(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#getAndRemoveAdd +# entryPoint: "jflex.state.StateSetQuickcheck.getAndRemoveAdd(Ljflex/state/StateSet;)V" +# - name: StateSetQuickcheck#enumerator +# entryPoint: "jflex.state.StateSetQuickcheck.enumerator(Ljflex/state/StateSet;)V" +# - name: CharClassesQuickcheck#invariants +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.invariants(Ljflex/core/unicode/CharClasses;)V" +# - name: CharClassesQuickcheck#maxCharCode +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.maxCharCode(Ljflex/core/unicode/CharClasses;)V" +# - name: CharClassesQuickcheck#addSetParts +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSetParts(Ljflex/core/unicode/CharClasses;Ljflex/core/unicode/IntCharSet;)V" +# - name: CharClassesQuickcheck#addSetComplement +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.addSetComplement(Ljflex/core/unicode/CharClasses;Ljflex/core/unicode/IntCharSet;)V" +# - name: CharClassesQuickcheck#normaliseSingle +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.normaliseSingle(Ljflex/core/unicode/CharClasses;I)V" +# - name: CharClassesQuickcheck#computeTablesEq +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.computeTablesEq(Ljflex/core/unicode/CharClasses;Ljava/util/ArrayList;)V" +# - name: CharClassesQuickcheck#getTablesEq +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.getTablesEq(Ljflex/core/unicode/CharClasses;Ljava/util/ArrayList;)V" +# - name: CharClassesQuickcheck#classCodesUnion +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.classCodesUnion(Ljflex/core/unicode/CharClasses;)V" +# - name: CharClassesQuickcheck#classCodesCode +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.classCodesCode(Ljflex/core/unicode/CharClasses;)V" +# - name: CharClassesQuickcheck#classCodesDisjointOrdered +# entryPoint: "jflex.core.unicode.CharClassesQuickcheck.classCodesDisjointOrdered(Ljflex/core/unicode/CharClasses;)V" diff --git a/artifacts/configs/mph-table-10/mph-table-10.patch b/artifacts/configs/mph-table-10/mph-table-10.patch new file mode 100644 index 00000000..fe2033f7 --- /dev/null +++ b/artifacts/configs/mph-table-10/mph-table-10.patch @@ -0,0 +1,93 @@ +diff --git a/pom.xml b/pom.xml +index 65f0f80..f704608 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -40,8 +40,75 @@ + 1.8 + + ++ ++ org.jacoco ++ jacoco-maven-plugin ++ 0.8.6 ++ ++ ++ default-prepare-agent ++ ++ prepare-agent ++ ++ ++ ++ jacoco-report ++ test ++ ++ report ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-jar-plugin ++ 3.2.0 ++ ++ ++ ++ test-jar ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-assembly-plugin ++ 3.3.0 ++ ++ ++ jar-with-dependencies ++ ++ ++ ++ ++ make-assembly ++ package ++ ++ single ++ ++ ++ ++ + + ++ ++ ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ ++ ++ ++ report ++ ++ ++ ++ ++ ++ + + + +diff --git a/src/test/java/com/indeed/mph/serializers/TestSmartListSerializer.java b/src/test/java/com/indeed/mph/serializers/TestSmartListSerializer.java +index 8312fe2..49fa01e 100644 +--- a/src/test/java/com/indeed/mph/serializers/TestSmartListSerializer.java ++++ b/src/test/java/com/indeed/mph/serializers/TestSmartListSerializer.java +@@ -11,7 +11,7 @@ import static com.indeed.mph.helpers.RoundTripHelpers.assertRoundTrip; + + @RunWith(JUnitQuickcheck.class) + public class TestSmartListSerializer { +- @Property ++ @Property(trials = 10) + public void canRoundTripSerializableLists( + final List intTarget, + final List byteTarget, diff --git a/artifacts/configs/mph-table-10/mph-table-10.yaml b/artifacts/configs/mph-table-10/mph-table-10.yaml new file mode 100644 index 00000000..3aedf219 --- /dev/null +++ b/artifacts/configs/mph-table-10/mph-table-10.yaml @@ -0,0 +1,10 @@ +name: mph-table-10 +URL: https://github.com/indeedeng/mph-table.git +checkoutID: dbd5413df33bf8f0a995822eeefe94df50f3c5a7 +patchName: artifacts/configs/mph-table-10/mph-table-10.patch +mainJar: mph-table-1.0.6-SNAPSHOT-jar-with-dependencies.jar +testJar: mph-table-1.0.6-SNAPSHOT-tests.jar +#mvnOptions: -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +properties: + - name: TestSmartListSerializer#canRoundTripSerializableLists + entryPoint: "com.indeed.mph.serializers.TestSmartListSerializer.canRoundTripSerializableLists(Ljava/util/List;Ljava/util/List;Ljava/util/List;)V" diff --git a/artifacts/configs/mph-table-1000/mph-table-1000.patch b/artifacts/configs/mph-table-1000/mph-table-1000.patch new file mode 100644 index 00000000..e354b3e2 --- /dev/null +++ b/artifacts/configs/mph-table-1000/mph-table-1000.patch @@ -0,0 +1,93 @@ +diff --git a/pom.xml b/pom.xml +index 65f0f80..f704608 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -40,8 +40,75 @@ + 1.8 + + ++ ++ org.jacoco ++ jacoco-maven-plugin ++ 0.8.6 ++ ++ ++ default-prepare-agent ++ ++ prepare-agent ++ ++ ++ ++ jacoco-report ++ test ++ ++ report ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-jar-plugin ++ 3.2.0 ++ ++ ++ ++ test-jar ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-assembly-plugin ++ 3.3.0 ++ ++ ++ jar-with-dependencies ++ ++ ++ ++ ++ make-assembly ++ package ++ ++ single ++ ++ ++ ++ + + ++ ++ ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ ++ ++ ++ report ++ ++ ++ ++ ++ ++ + + + +diff --git a/src/test/java/com/indeed/mph/serializers/TestSmartListSerializer.java b/src/test/java/com/indeed/mph/serializers/TestSmartListSerializer.java +index 8312fe2..49fa01e 100644 +--- a/src/test/java/com/indeed/mph/serializers/TestSmartListSerializer.java ++++ b/src/test/java/com/indeed/mph/serializers/TestSmartListSerializer.java +@@ -11,7 +11,7 @@ import static com.indeed.mph.helpers.RoundTripHelpers.assertRoundTrip; + + @RunWith(JUnitQuickcheck.class) + public class TestSmartListSerializer { +- @Property ++ @Property(trials = 1000) + public void canRoundTripSerializableLists( + final List intTarget, + final List byteTarget, diff --git a/artifacts/configs/mph-table-1000/mph-table-1000.yaml b/artifacts/configs/mph-table-1000/mph-table-1000.yaml new file mode 100644 index 00000000..fcd85b49 --- /dev/null +++ b/artifacts/configs/mph-table-1000/mph-table-1000.yaml @@ -0,0 +1,10 @@ +name: mph-table-1000 +URL: https://github.com/indeedeng/mph-table.git +checkoutID: dbd5413df33bf8f0a995822eeefe94df50f3c5a7 +patchName: artifacts/configs/mph-table-1000/mph-table-1000.patch +mainJar: mph-table-1.0.6-SNAPSHOT-jar-with-dependencies.jar +testJar: mph-table-1.0.6-SNAPSHOT-tests.jar +#mvnOptions: -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +properties: + - name: TestSmartListSerializer#canRoundTripSerializableLists + entryPoint: "com.indeed.mph.serializers.TestSmartListSerializer.canRoundTripSerializableLists(Ljava/util/List;Ljava/util/List;Ljava/util/List;)V" diff --git a/artifacts/configs/mph-table-50/mph-table-50.patch b/artifacts/configs/mph-table-50/mph-table-50.patch new file mode 100644 index 00000000..7e6b930f --- /dev/null +++ b/artifacts/configs/mph-table-50/mph-table-50.patch @@ -0,0 +1,93 @@ +diff --git a/pom.xml b/pom.xml +index 65f0f80..f704608 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -40,8 +40,75 @@ + 1.8 + + ++ ++ org.jacoco ++ jacoco-maven-plugin ++ 0.8.6 ++ ++ ++ default-prepare-agent ++ ++ prepare-agent ++ ++ ++ ++ jacoco-report ++ test ++ ++ report ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-jar-plugin ++ 3.2.0 ++ ++ ++ ++ test-jar ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-assembly-plugin ++ 3.3.0 ++ ++ ++ jar-with-dependencies ++ ++ ++ ++ ++ make-assembly ++ package ++ ++ single ++ ++ ++ ++ + + ++ ++ ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ ++ ++ ++ report ++ ++ ++ ++ ++ ++ + + + +diff --git a/src/test/java/com/indeed/mph/serializers/TestSmartListSerializer.java b/src/test/java/com/indeed/mph/serializers/TestSmartListSerializer.java +index 8312fe2..49fa01e 100644 +--- a/src/test/java/com/indeed/mph/serializers/TestSmartListSerializer.java ++++ b/src/test/java/com/indeed/mph/serializers/TestSmartListSerializer.java +@@ -11,7 +11,7 @@ import static com.indeed.mph.helpers.RoundTripHelpers.assertRoundTrip; + + @RunWith(JUnitQuickcheck.class) + public class TestSmartListSerializer { +- @Property ++ @Property(trials = 50) + public void canRoundTripSerializableLists( + final List intTarget, + final List byteTarget, diff --git a/artifacts/configs/mph-table-50/mph-table-50.yaml b/artifacts/configs/mph-table-50/mph-table-50.yaml new file mode 100644 index 00000000..64c242ed --- /dev/null +++ b/artifacts/configs/mph-table-50/mph-table-50.yaml @@ -0,0 +1,10 @@ +name: mph-table-50 +URL: https://github.com/indeedeng/mph-table.git +checkoutID: dbd5413df33bf8f0a995822eeefe94df50f3c5a7 +patchName: artifacts/configs/mph-table-50/mph-table-50.patch +mainJar: mph-table-1.0.6-SNAPSHOT-jar-with-dependencies.jar +testJar: mph-table-1.0.6-SNAPSHOT-tests.jar +#mvnOptions: -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +properties: + - name: TestSmartListSerializer#canRoundTripSerializableLists + entryPoint: "com.indeed.mph.serializers.TestSmartListSerializer.canRoundTripSerializableLists(Ljava/util/List;Ljava/util/List;Ljava/util/List;)V" diff --git a/artifacts/configs/mph-table-500/mph-table-500.patch b/artifacts/configs/mph-table-500/mph-table-500.patch new file mode 100644 index 00000000..d211adac --- /dev/null +++ b/artifacts/configs/mph-table-500/mph-table-500.patch @@ -0,0 +1,93 @@ +diff --git a/pom.xml b/pom.xml +index 65f0f80..f704608 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -40,8 +40,75 @@ + 1.8 + + ++ ++ org.jacoco ++ jacoco-maven-plugin ++ 0.8.6 ++ ++ ++ default-prepare-agent ++ ++ prepare-agent ++ ++ ++ ++ jacoco-report ++ test ++ ++ report ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-jar-plugin ++ 3.2.0 ++ ++ ++ ++ test-jar ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-assembly-plugin ++ 3.3.0 ++ ++ ++ jar-with-dependencies ++ ++ ++ ++ ++ make-assembly ++ package ++ ++ single ++ ++ ++ ++ + + ++ ++ ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ ++ ++ ++ report ++ ++ ++ ++ ++ ++ + + + +diff --git a/src/test/java/com/indeed/mph/serializers/TestSmartListSerializer.java b/src/test/java/com/indeed/mph/serializers/TestSmartListSerializer.java +index 8312fe2..49fa01e 100644 +--- a/src/test/java/com/indeed/mph/serializers/TestSmartListSerializer.java ++++ b/src/test/java/com/indeed/mph/serializers/TestSmartListSerializer.java +@@ -11,7 +11,7 @@ import static com.indeed.mph.helpers.RoundTripHelpers.assertRoundTrip; + + @RunWith(JUnitQuickcheck.class) + public class TestSmartListSerializer { +- @Property ++ @Property(trials = 500) + public void canRoundTripSerializableLists( + final List intTarget, + final List byteTarget, diff --git a/artifacts/configs/mph-table-500/mph-table-500.yaml b/artifacts/configs/mph-table-500/mph-table-500.yaml new file mode 100644 index 00000000..00573bd7 --- /dev/null +++ b/artifacts/configs/mph-table-500/mph-table-500.yaml @@ -0,0 +1,10 @@ +name: mph-table-500 +URL: https://github.com/indeedeng/mph-table.git +checkoutID: dbd5413df33bf8f0a995822eeefe94df50f3c5a7 +patchName: artifacts/configs/mph-table-500/mph-table-500.patch +mainJar: mph-table-1.0.6-SNAPSHOT-jar-with-dependencies.jar +testJar: mph-table-1.0.6-SNAPSHOT-tests.jar +#mvnOptions: -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +properties: + - name: TestSmartListSerializer#canRoundTripSerializableLists + entryPoint: "com.indeed.mph.serializers.TestSmartListSerializer.canRoundTripSerializableLists(Ljava/util/List;Ljava/util/List;Ljava/util/List;)V" diff --git a/artifacts/configs/mph-table-fixed/mph-table-fixed.patch b/artifacts/configs/mph-table-fixed/mph-table-fixed.patch new file mode 100644 index 00000000..2c55cfeb --- /dev/null +++ b/artifacts/configs/mph-table-fixed/mph-table-fixed.patch @@ -0,0 +1,161 @@ +diff --git a/pom.xml b/pom.xml +--- a/pom.xml (revision dbd5413df33bf8f0a995822eeefe94df50f3c5a7) ++++ b/pom.xml (date 1655257746259) +@@ -40,8 +40,75 @@ + 1.8 + + ++ ++ org.jacoco ++ jacoco-maven-plugin ++ 0.8.6 ++ ++ ++ default-prepare-agent ++ ++ prepare-agent ++ ++ ++ ++ jacoco-report ++ test ++ ++ report ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-jar-plugin ++ 3.2.0 ++ ++ ++ ++ test-jar ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-assembly-plugin ++ 3.3.0 ++ ++ ++ jar-with-dependencies ++ ++ ++ ++ ++ make-assembly ++ package ++ ++ single ++ ++ ++ ++ + + ++ ++ ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ ++ ++ ++ report ++ ++ ++ ++ ++ ++ + + + +diff --git a/src/test/java/com/indeed/mph/generators/IntListGenerator.java b/src/test/java/com/indeed/mph/generators/IntListGenerator.java +new file mode 100644 +index 0000000..33b6870 +--- /dev/null ++++ b/src/test/java/com/indeed/mph/generators/IntListGenerator.java +@@ -0,0 +1,36 @@ ++package com.indeed.mph.generators; ++import com.pholser.junit.quickcheck.generator.ComponentizedGenerator; ++import com.pholser.junit.quickcheck.generator.GenerationStatus; ++import com.pholser.junit.quickcheck.random.SourceOfRandomness; ++import java.util.ArrayList; ++import java.util.List; ++import java.util.stream.Collectors; ++import java.util.stream.IntStream; ++ ++public class IntListGenerator extends ComponentizedGenerator { ++ public IntListGenerator() { ++ super(List.class); ++ } ++ boolean generatedEmptyList = false; ++ @Override ++ public List generate(SourceOfRandomness sourceOfRandomness, GenerationStatus generationStatus) { ++ if (!generatedEmptyList) { ++ generatedEmptyList = true; ++ return new ArrayList(); ++ } ++ int rng = sourceOfRandomness.nextInt(0, 20); ++ int listSize = 0; ++ if (rng >= 0 && rng <= 16) { ++ listSize = sourceOfRandomness.nextInt(0, 100); ++ } else if (rng >= 17 && rng <= 18) { ++ listSize = sourceOfRandomness.nextInt(1000, 10000); ++ } else if (rng >= 19 && rng <= 20) { ++ listSize = sourceOfRandomness.nextInt(100000, 10000000); ++ } ++ return IntStream.range(0, listSize).mapToObj(i -> sourceOfRandomness.nextInt(Integer.MIN_VALUE, Integer.MAX_VALUE)).collect(Collectors.toList()); ++ } ++ @Override ++ public int numberOfNeededComponents() { ++ return 1; ++ } ++} +diff --git a/src/test/java/com/indeed/mph/serializers/TestSmartListSerializer.java b/src/test/java/com/indeed/mph/serializers/TestSmartListSerializer.java +index 8312fe2..e898509 100644 +--- a/src/test/java/com/indeed/mph/serializers/TestSmartListSerializer.java ++++ b/src/test/java/com/indeed/mph/serializers/TestSmartListSerializer.java +@@ -1,9 +1,9 @@ + package com.indeed.mph.serializers; +- ++import com.pholser.junit.quickcheck.From; + import com.pholser.junit.quickcheck.Property; + import com.pholser.junit.quickcheck.runner.JUnitQuickcheck; + import org.junit.runner.RunWith; +- ++import com.indeed.mph.generators.IntListGenerator; + import java.io.IOException; + import java.util.List; + +@@ -23,7 +23,23 @@ public class TestSmartListSerializer { + final SmartListSerializer bytesSerializer = new SmartListSerializer<>(new SmartByteSerializer()); + assertRoundTrip(bytesSerializer, byteTarget); + ++ final SmartListSerializer stringsSerializer = new SmartListSerializer<>(new SmartStringSerializer()); ++ assertRoundTrip(stringsSerializer, stringTarget); ++ } ++ @Property ++ public void canRoundTripSerializableListsWithGenerator( ++ @From(IntListGenerator.class) final List intTarget, ++ final List byteTarget, ++ final List stringTarget ++ ) throws IOException { ++ final SmartListSerializer intsSerializer = new SmartListSerializer<>(new SmartIntegerSerializer()); ++ assertRoundTrip(intsSerializer, intTarget); ++ ++ final SmartListSerializer bytesSerializer = new SmartListSerializer<>(new SmartByteSerializer()); ++ assertRoundTrip(bytesSerializer, byteTarget); ++ + final SmartListSerializer stringsSerializer = new SmartListSerializer<>(new SmartStringSerializer()); + assertRoundTrip(stringsSerializer, stringTarget); + } + } ++ diff --git a/artifacts/configs/mph-table-fixed/mph-table-fixed.yaml b/artifacts/configs/mph-table-fixed/mph-table-fixed.yaml new file mode 100644 index 00000000..45e64d46 --- /dev/null +++ b/artifacts/configs/mph-table-fixed/mph-table-fixed.yaml @@ -0,0 +1,26 @@ +name: mph-table-fixed +URL: https://github.com/indeedeng/mph-table.git +checkoutID: dbd5413df33bf8f0a995822eeefe94df50f3c5a7 +patchName: artifacts/configs/mph-table-fixed/mph-table-fixed.patch +mainJar: mph-table-1.0.6-SNAPSHOT-jar-with-dependencies.jar +testJar: mph-table-1.0.6-SNAPSHOT-tests.jar +#mvnOptions: -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +properties: +# - name: TestSmartListSerializer#canRoundTripSerializableLists +# entryPoint: "com.indeed.mph.serializers.TestSmartListSerializer.canRoundTripSerializableLists(Ljava/util/List;Ljava/util/List;Ljava/util/List;)V" + - name: TestSmartListSerializer#canRoundTripSerializableListsWithGenerator + entryPoint: "com.indeed.mph.serializers.TestSmartListSerializer.canRoundTripSerializableListsWithGenerator(Ljava/util/List;Ljava/util/List;Ljava/util/List;)V" +# - name: TestSmartShortSerializer#canRoundTripShort +# entryPoint: "com.indeed.mph.serializers.TestSmartShortSerializer.canRoundTripShort(S)V" +# - name: TestSmartIntegerSerializer#canRoundTripIntegers +# entryPoint: "com.indeed.mph.serializers.TestSmartIntegerSerializer.canRoundTripIntegers(I)V" +# - name: TestSmartStringSerializer#canRoundTripStrings +# entryPoint: "com.indeed.mph.serializers.TestSmartStringSerializer.canRoundTripStrings(Ljava/lang/String;)V" +# - name: TestSmartByteSerializer#canRoundTripBytes +# entryPoint: "com.indeed.mph.serializers.TestSmartByteSerializer.canRoundTripBytes(B)V" +# - name: TestSmartLongSerializer#canRoundTripLongs +# entryPoint: "com.indeed.mph.serializers.TestSmartLongSerializer.canRoundTripLongs(J)V" +# - name: TestSmartPairSerializer#canRoundTripPairs +# entryPoint: "com.indeed.mph.serializers.TestSmartPairSerializer.canRoundTripPairs(Lcom/indeed/util/core/Pair;)V" +# - name: TestSmartOptionalSerializer#canRoundTripPresentOptionals +# entryPoint: "com.indeed.mph.serializers.TestSmartOptionalSerializer.canRoundTripPresentOptionals(J)V" diff --git a/artifacts/configs/mph-table-naive/mph-table-naive.patch b/artifacts/configs/mph-table-naive/mph-table-naive.patch new file mode 100644 index 00000000..a8b98812 --- /dev/null +++ b/artifacts/configs/mph-table-naive/mph-table-naive.patch @@ -0,0 +1,112 @@ +diff --git a/pom.xml b/pom.xml +index 65f0f80..f704608 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -40,8 +40,75 @@ + 1.8 + + ++ ++ org.jacoco ++ jacoco-maven-plugin ++ 0.8.6 ++ ++ ++ default-prepare-agent ++ ++ prepare-agent ++ ++ ++ ++ jacoco-report ++ test ++ ++ report ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-jar-plugin ++ 3.2.0 ++ ++ ++ ++ test-jar ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-assembly-plugin ++ 3.3.0 ++ ++ ++ jar-with-dependencies ++ ++ ++ ++ ++ make-assembly ++ package ++ ++ single ++ ++ ++ ++ + + ++ ++ ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ ++ ++ ++ report ++ ++ ++ ++ ++ ++ + + + +diff --git a/src/test/java/com/indeed/mph/serializers/TestSmartListSerializer.java b/src/test/java/com/indeed/mph/serializers/TestSmartListSerializer.java +index 8312fe2..3a31bdf 100644 +--- a/src/test/java/com/indeed/mph/serializers/TestSmartListSerializer.java ++++ b/src/test/java/com/indeed/mph/serializers/TestSmartListSerializer.java +@@ -1,5 +1,6 @@ + package com.indeed.mph.serializers; + ++import com.pholser.junit.quickcheck.generator.Size; + import com.pholser.junit.quickcheck.Property; + import com.pholser.junit.quickcheck.runner.JUnitQuickcheck; + import org.junit.runner.RunWith; +@@ -26,4 +27,20 @@ public class TestSmartListSerializer { + final SmartListSerializer stringsSerializer = new SmartListSerializer<>(new SmartStringSerializer()); + assertRoundTrip(stringsSerializer, stringTarget); + } ++ ++ @Property ++ public void canRoundTripSerializableListsNaive( ++ @Size(min=0, max=10000000) final List intTarget, ++ final List byteTarget, ++ final List stringTarget ++ ) throws IOException { ++ final SmartListSerializer intsSerializer = new SmartListSerializer<>(new SmartIntegerSerializer()); ++ assertRoundTrip(intsSerializer, intTarget); ++ ++ final SmartListSerializer bytesSerializer = new SmartListSerializer<>(new SmartByteSerializer()); ++ assertRoundTrip(bytesSerializer, byteTarget); ++ ++ final SmartListSerializer stringsSerializer = new SmartListSerializer<>(new SmartStringSerializer()); ++ assertRoundTrip(stringsSerializer, stringTarget); ++ } + } diff --git a/artifacts/configs/mph-table-naive/mph-table-naive.yaml b/artifacts/configs/mph-table-naive/mph-table-naive.yaml new file mode 100644 index 00000000..4899e6a3 --- /dev/null +++ b/artifacts/configs/mph-table-naive/mph-table-naive.yaml @@ -0,0 +1,26 @@ +name: mph-table-naive +URL: https://github.com/indeedeng/mph-table.git +checkoutID: dbd5413df33bf8f0a995822eeefe94df50f3c5a7 +patchName: artifacts/configs/mph-table-naive/mph-table-naive.patch +mainJar: mph-table-1.0.6-SNAPSHOT-jar-with-dependencies.jar +testJar: mph-table-1.0.6-SNAPSHOT-tests.jar +#mvnOptions: -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +properties: + - name: TestSmartListSerializer#canRoundTripSerializableLists + entryPoint: "com.indeed.mph.serializers.TestSmartListSerializer.canRoundTripSerializableLists(Ljava/util/List;Ljava/util/List;Ljava/util/List;)V" + - name: TestSmartListSerializer#canRoundTripSerializableListsNaive + entryPoint: "com.indeed.mph.serializers.TestSmartListSerializer.canRoundTripSerializableListsNaive(Ljava/util/List;Ljava/util/List;Ljava/util/List;)V" + - name: TestSmartShortSerializer#canRoundTripShort + entryPoint: "com.indeed.mph.serializers.TestSmartShortSerializer.canRoundTripShort(S)V" + - name: TestSmartIntegerSerializer#canRoundTripIntegers + entryPoint: "com.indeed.mph.serializers.TestSmartIntegerSerializer.canRoundTripIntegers(I)V" + - name: TestSmartStringSerializer#canRoundTripStrings + entryPoint: "com.indeed.mph.serializers.TestSmartStringSerializer.canRoundTripStrings(Ljava/lang/String;)V" + - name: TestSmartByteSerializer#canRoundTripBytes + entryPoint: "com.indeed.mph.serializers.TestSmartByteSerializer.canRoundTripBytes(B)V" + - name: TestSmartLongSerializer#canRoundTripLongs + entryPoint: "com.indeed.mph.serializers.TestSmartLongSerializer.canRoundTripLongs(J)V" + - name: TestSmartPairSerializer#canRoundTripPairs + entryPoint: "com.indeed.mph.serializers.TestSmartPairSerializer.canRoundTripPairs(Lcom/indeed/util/core/Pair;)V" + - name: TestSmartOptionalSerializer#canRoundTripPresentOptionals + entryPoint: "com.indeed.mph.serializers.TestSmartOptionalSerializer.canRoundTripPresentOptionals(J)V" diff --git a/artifacts/configs/mph-table/mph-table.patch b/artifacts/configs/mph-table/mph-table.patch new file mode 100644 index 00000000..71f0002c --- /dev/null +++ b/artifacts/configs/mph-table/mph-table.patch @@ -0,0 +1,80 @@ +diff --git a/pom.xml b/pom.xml +--- a/pom.xml (revision dbd5413df33bf8f0a995822eeefe94df50f3c5a7) ++++ b/pom.xml (date 1655257746259) +@@ -40,8 +40,75 @@ + 1.8 + + ++ ++ org.jacoco ++ jacoco-maven-plugin ++ 0.8.6 ++ ++ ++ default-prepare-agent ++ ++ prepare-agent ++ ++ ++ ++ jacoco-report ++ test ++ ++ report ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-jar-plugin ++ 3.2.0 ++ ++ ++ ++ test-jar ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-assembly-plugin ++ 3.3.0 ++ ++ ++ jar-with-dependencies ++ ++ ++ ++ ++ make-assembly ++ package ++ ++ single ++ ++ ++ ++ + + ++ ++ ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ ++ ++ ++ report ++ ++ ++ ++ ++ ++ + + + + diff --git a/artifacts/configs/mph-table/mph-table.yaml b/artifacts/configs/mph-table/mph-table.yaml new file mode 100644 index 00000000..2b1c8cf7 --- /dev/null +++ b/artifacts/configs/mph-table/mph-table.yaml @@ -0,0 +1,24 @@ +name: mph-table +URL: https://github.com/indeedeng/mph-table.git +checkoutID: dbd5413df33bf8f0a995822eeefe94df50f3c5a7 +patchName: artifacts/configs/mph-table/mph-table.patch +mainJar: mph-table-1.0.6-SNAPSHOT-jar-with-dependencies.jar +testJar: mph-table-1.0.6-SNAPSHOT-tests.jar +#mvnOptions: -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +properties: + - name: TestSmartListSerializer#canRoundTripSerializableLists + entryPoint: "com.indeed.mph.serializers.TestSmartListSerializer.canRoundTripSerializableLists(Ljava/util/List;Ljava/util/List;Ljava/util/List;)V" +# - name: TestSmartShortSerializer#canRoundTripShort +# entryPoint: "com.indeed.mph.serializers.TestSmartShortSerializer.canRoundTripShort(S)V" +# - name: TestSmartIntegerSerializer#canRoundTripIntegers +# entryPoint: "com.indeed.mph.serializers.TestSmartIntegerSerializer.canRoundTripIntegers(I)V" +# - name: TestSmartStringSerializer#canRoundTripStrings +# entryPoint: "com.indeed.mph.serializers.TestSmartStringSerializer.canRoundTripStrings(Ljava/lang/String;)V" +# - name: TestSmartByteSerializer#canRoundTripBytes +# entryPoint: "com.indeed.mph.serializers.TestSmartByteSerializer.canRoundTripBytes(B)V" +# - name: TestSmartLongSerializer#canRoundTripLongs +# entryPoint: "com.indeed.mph.serializers.TestSmartLongSerializer.canRoundTripLongs(J)V" +# - name: TestSmartPairSerializer#canRoundTripPairs +# entryPoint: "com.indeed.mph.serializers.TestSmartPairSerializer.canRoundTripPairs(Lcom/indeed/util/core/Pair;)V" +# - name: TestSmartOptionalSerializer#canRoundTripPresentOptionals +# entryPoint: "com.indeed.mph.serializers.TestSmartOptionalSerializer.canRoundTripPresentOptionals(J)V" diff --git a/artifacts/configs/rpki-commons-10/rpki-commons-10.patch b/artifacts/configs/rpki-commons-10/rpki-commons-10.patch new file mode 100644 index 00000000..5346a396 --- /dev/null +++ b/artifacts/configs/rpki-commons-10/rpki-commons-10.patch @@ -0,0 +1,97 @@ +diff --git a/pom.xml b/pom.xml +index 08ebb666..0157a994 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -373,6 +373,58 @@ + true + + ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ 0.8.6 ++ ++ ++ default-prepare-agent ++ ++ prepare-agent ++ ++ ++ ++ jacoco-report ++ test ++ ++ report ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-jar-plugin ++ 3.2.0 ++ ++ ++ ++ test-jar ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-assembly-plugin ++ 3.3.0 ++ ++ ++ jar-with-dependencies ++ ++ ++ ++ ++ make-assembly ++ package ++ ++ single ++ ++ ++ ++ + + + +@@ -457,4 +509,20 @@ + + + ++ ++ ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ ++ ++ ++ report ++ ++ ++ ++ ++ ++ + +diff --git a/src/test/java/net/ripe/rpki/commons/validation/X509ResourceCertificateParentChildValidatorTest.java b/src/test/java/net/ripe/rpki/commons/validation/X509ResourceCertificateParentChildValidatorTest.java +index cb8b7dd9..eea76dcf 100644 +--- a/src/test/java/net/ripe/rpki/commons/validation/X509ResourceCertificateParentChildValidatorTest.java ++++ b/src/test/java/net/ripe/rpki/commons/validation/X509ResourceCertificateParentChildValidatorTest.java +@@ -222,7 +222,7 @@ public class X509ResourceCertificateParentChildValidatorTest { + assertTrue(result.hasFailures()); + } + +- @Property ++ @Property(trials = 10) + public void validParentChildSubResources(List<@From(IpResourceGen.class) IpResource> parentResources, int childResourceCount) { + assumeThat(parentResources.size(), greaterThan(0)); + assumeThat(childResourceCount, greaterThan(0)); diff --git a/artifacts/configs/rpki-commons-10/rpki-commons-10.yaml b/artifacts/configs/rpki-commons-10/rpki-commons-10.yaml new file mode 100644 index 00000000..f6705678 --- /dev/null +++ b/artifacts/configs/rpki-commons-10/rpki-commons-10.yaml @@ -0,0 +1,21 @@ +name: rpki-commons-10 +URL: https://github.com/RIPE-NCC/rpki-commons.git +checkoutID: dd5af7c644d2cd9cc6b0b5f4f2480b6dfc1ef074 +patchName: artifacts/configs/rpki-commons-10/rpki-commons-10.patch +mainJar: rpki-commons-DEV.jar +testJar: rpki-commons-DEV-tests.jar +#mvnOptions: -DfailIfNoTests=false +#mvnOptions: -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +properties: +# - name: RoaCMSBuilderPropertyTest#buildEncodedParseCheck +# entryPoint: net.ripe.rpki.commons.crypto.cms.roa.RoaCMSBuilderPropertyTest.buildEncodedParseCheck(JLjava/lang/Integer;)V +# - name: ManifestCMSBuilderPropertyTest#buildEncodedParseCheck +# entryPoint: net.ripe.rpki.commons.crypto.cms.manifest.ManifestCMSBuilderPropertyTest.buildEncodedParseCheck([BLjava/math/BigInteger;Ljava/lang/Integer;)V +# - name: AspaCmsTest#should_generate_aspa +# entryPoint: net.ripe.rpki.commons.crypto.cms.aspa.AspaCmsTest.should_generate_aspa(ILjava/util/List;)V + - name: X509ResourceCertificateParentChildValidatorTest#validParentChildSubResources + entryPoint: net.ripe.rpki.commons.validation.X509ResourceCertificateParentChildValidatorTest.validParentChildSubResources(Ljava/util/List;I)V +# - name: X509ResourceCertificateParentChildValidatorTest#validParentChildOverClaiming +# entryPoint: net.ripe.rpki.commons.validation.X509ResourceCertificateParentChildValidatorTest.validParentChildOverClaiming(Ljava/util/List;ILjava/util/List;)V +# - name: X509ResourceCertificateParentChildValidatorTest#validParentChildOverClaimingLooseValidation +# entryPoint: net.ripe.rpki.commons.validation.X509ResourceCertificateParentChildValidatorTest.validParentChildOverClaimingLooseValidation(Ljava/util/List;ILjava/util/List;)V diff --git a/artifacts/configs/rpki-commons-1000/rpki-commons-1000.patch b/artifacts/configs/rpki-commons-1000/rpki-commons-1000.patch new file mode 100644 index 00000000..454ddf8d --- /dev/null +++ b/artifacts/configs/rpki-commons-1000/rpki-commons-1000.patch @@ -0,0 +1,97 @@ +diff --git a/pom.xml b/pom.xml +index 08ebb666..0157a994 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -373,6 +373,58 @@ + true + + ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ 0.8.6 ++ ++ ++ default-prepare-agent ++ ++ prepare-agent ++ ++ ++ ++ jacoco-report ++ test ++ ++ report ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-jar-plugin ++ 3.2.0 ++ ++ ++ ++ test-jar ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-assembly-plugin ++ 3.3.0 ++ ++ ++ jar-with-dependencies ++ ++ ++ ++ ++ make-assembly ++ package ++ ++ single ++ ++ ++ ++ + + + +@@ -457,4 +509,20 @@ + + + ++ ++ ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ ++ ++ ++ report ++ ++ ++ ++ ++ ++ + +diff --git a/src/test/java/net/ripe/rpki/commons/validation/X509ResourceCertificateParentChildValidatorTest.java b/src/test/java/net/ripe/rpki/commons/validation/X509ResourceCertificateParentChildValidatorTest.java +index cb8b7dd9..eea76dcf 100644 +--- a/src/test/java/net/ripe/rpki/commons/validation/X509ResourceCertificateParentChildValidatorTest.java ++++ b/src/test/java/net/ripe/rpki/commons/validation/X509ResourceCertificateParentChildValidatorTest.java +@@ -222,7 +222,7 @@ public class X509ResourceCertificateParentChildValidatorTest { + assertTrue(result.hasFailures()); + } + +- @Property ++ @Property(trials = 1000) + public void validParentChildSubResources(List<@From(IpResourceGen.class) IpResource> parentResources, int childResourceCount) { + assumeThat(parentResources.size(), greaterThan(0)); + assumeThat(childResourceCount, greaterThan(0)); diff --git a/artifacts/configs/rpki-commons-1000/rpki-commons-1000.yaml b/artifacts/configs/rpki-commons-1000/rpki-commons-1000.yaml new file mode 100644 index 00000000..d1530e4b --- /dev/null +++ b/artifacts/configs/rpki-commons-1000/rpki-commons-1000.yaml @@ -0,0 +1,21 @@ +name: rpki-commons-1000 +URL: https://github.com/RIPE-NCC/rpki-commons.git +checkoutID: dd5af7c644d2cd9cc6b0b5f4f2480b6dfc1ef074 +patchName: artifacts/configs/rpki-commons-1000/rpki-commons-1000.patch +mainJar: rpki-commons-DEV.jar +testJar: rpki-commons-DEV-tests.jar +#mvnOptions: -DfailIfNoTests=false +#mvnOptions: -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +properties: +# - name: RoaCMSBuilderPropertyTest#buildEncodedParseCheck +# entryPoint: net.ripe.rpki.commons.crypto.cms.roa.RoaCMSBuilderPropertyTest.buildEncodedParseCheck(JLjava/lang/Integer;)V +# - name: ManifestCMSBuilderPropertyTest#buildEncodedParseCheck +# entryPoint: net.ripe.rpki.commons.crypto.cms.manifest.ManifestCMSBuilderPropertyTest.buildEncodedParseCheck([BLjava/math/BigInteger;Ljava/lang/Integer;)V +# - name: AspaCmsTest#should_generate_aspa +# entryPoint: net.ripe.rpki.commons.crypto.cms.aspa.AspaCmsTest.should_generate_aspa(ILjava/util/List;)V + - name: X509ResourceCertificateParentChildValidatorTest#validParentChildSubResources + entryPoint: net.ripe.rpki.commons.validation.X509ResourceCertificateParentChildValidatorTest.validParentChildSubResources(Ljava/util/List;I)V +# - name: X509ResourceCertificateParentChildValidatorTest#validParentChildOverClaiming +# entryPoint: net.ripe.rpki.commons.validation.X509ResourceCertificateParentChildValidatorTest.validParentChildOverClaiming(Ljava/util/List;ILjava/util/List;)V +# - name: X509ResourceCertificateParentChildValidatorTest#validParentChildOverClaimingLooseValidation +# entryPoint: net.ripe.rpki.commons.validation.X509ResourceCertificateParentChildValidatorTest.validParentChildOverClaimingLooseValidation(Ljava/util/List;ILjava/util/List;)V diff --git a/artifacts/configs/rpki-commons-50/rpki-commons-50.patch b/artifacts/configs/rpki-commons-50/rpki-commons-50.patch new file mode 100644 index 00000000..51af02b3 --- /dev/null +++ b/artifacts/configs/rpki-commons-50/rpki-commons-50.patch @@ -0,0 +1,97 @@ +diff --git a/pom.xml b/pom.xml +index 08ebb666..0157a994 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -373,6 +373,58 @@ + true + + ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ 0.8.6 ++ ++ ++ default-prepare-agent ++ ++ prepare-agent ++ ++ ++ ++ jacoco-report ++ test ++ ++ report ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-jar-plugin ++ 3.2.0 ++ ++ ++ ++ test-jar ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-assembly-plugin ++ 3.3.0 ++ ++ ++ jar-with-dependencies ++ ++ ++ ++ ++ make-assembly ++ package ++ ++ single ++ ++ ++ ++ + + + +@@ -457,4 +509,20 @@ + + + ++ ++ ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ ++ ++ ++ report ++ ++ ++ ++ ++ ++ + +diff --git a/src/test/java/net/ripe/rpki/commons/validation/X509ResourceCertificateParentChildValidatorTest.java b/src/test/java/net/ripe/rpki/commons/validation/X509ResourceCertificateParentChildValidatorTest.java +index cb8b7dd9..eea76dcf 100644 +--- a/src/test/java/net/ripe/rpki/commons/validation/X509ResourceCertificateParentChildValidatorTest.java ++++ b/src/test/java/net/ripe/rpki/commons/validation/X509ResourceCertificateParentChildValidatorTest.java +@@ -222,7 +222,7 @@ public class X509ResourceCertificateParentChildValidatorTest { + assertTrue(result.hasFailures()); + } + +- @Property ++ @Property(trials = 50) + public void validParentChildSubResources(List<@From(IpResourceGen.class) IpResource> parentResources, int childResourceCount) { + assumeThat(parentResources.size(), greaterThan(0)); + assumeThat(childResourceCount, greaterThan(0)); diff --git a/artifacts/configs/rpki-commons-50/rpki-commons-50.yaml b/artifacts/configs/rpki-commons-50/rpki-commons-50.yaml new file mode 100644 index 00000000..41dbb41f --- /dev/null +++ b/artifacts/configs/rpki-commons-50/rpki-commons-50.yaml @@ -0,0 +1,21 @@ +name: rpki-commons-50 +URL: https://github.com/RIPE-NCC/rpki-commons.git +checkoutID: dd5af7c644d2cd9cc6b0b5f4f2480b6dfc1ef074 +patchName: artifacts/configs/rpki-commons-50/rpki-commons-50.patch +mainJar: rpki-commons-DEV.jar +testJar: rpki-commons-DEV-tests.jar +#mvnOptions: -DfailIfNoTests=false +#mvnOptions: -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +properties: +# - name: RoaCMSBuilderPropertyTest#buildEncodedParseCheck +# entryPoint: net.ripe.rpki.commons.crypto.cms.roa.RoaCMSBuilderPropertyTest.buildEncodedParseCheck(JLjava/lang/Integer;)V +# - name: ManifestCMSBuilderPropertyTest#buildEncodedParseCheck +# entryPoint: net.ripe.rpki.commons.crypto.cms.manifest.ManifestCMSBuilderPropertyTest.buildEncodedParseCheck([BLjava/math/BigInteger;Ljava/lang/Integer;)V +# - name: AspaCmsTest#should_generate_aspa +# entryPoint: net.ripe.rpki.commons.crypto.cms.aspa.AspaCmsTest.should_generate_aspa(ILjava/util/List;)V + - name: X509ResourceCertificateParentChildValidatorTest#validParentChildSubResources + entryPoint: net.ripe.rpki.commons.validation.X509ResourceCertificateParentChildValidatorTest.validParentChildSubResources(Ljava/util/List;I)V +# - name: X509ResourceCertificateParentChildValidatorTest#validParentChildOverClaiming +# entryPoint: net.ripe.rpki.commons.validation.X509ResourceCertificateParentChildValidatorTest.validParentChildOverClaiming(Ljava/util/List;ILjava/util/List;)V +# - name: X509ResourceCertificateParentChildValidatorTest#validParentChildOverClaimingLooseValidation +# entryPoint: net.ripe.rpki.commons.validation.X509ResourceCertificateParentChildValidatorTest.validParentChildOverClaimingLooseValidation(Ljava/util/List;ILjava/util/List;)V diff --git a/artifacts/configs/rpki-commons-500/rpki-commons-500.patch b/artifacts/configs/rpki-commons-500/rpki-commons-500.patch new file mode 100644 index 00000000..5f2285e5 --- /dev/null +++ b/artifacts/configs/rpki-commons-500/rpki-commons-500.patch @@ -0,0 +1,97 @@ +diff --git a/pom.xml b/pom.xml +index 08ebb666..0157a994 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -373,6 +373,58 @@ + true + + ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ 0.8.6 ++ ++ ++ default-prepare-agent ++ ++ prepare-agent ++ ++ ++ ++ jacoco-report ++ test ++ ++ report ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-jar-plugin ++ 3.2.0 ++ ++ ++ ++ test-jar ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-assembly-plugin ++ 3.3.0 ++ ++ ++ jar-with-dependencies ++ ++ ++ ++ ++ make-assembly ++ package ++ ++ single ++ ++ ++ ++ + + + +@@ -457,4 +509,20 @@ + + + ++ ++ ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ ++ ++ ++ report ++ ++ ++ ++ ++ ++ + +diff --git a/src/test/java/net/ripe/rpki/commons/validation/X509ResourceCertificateParentChildValidatorTest.java b/src/test/java/net/ripe/rpki/commons/validation/X509ResourceCertificateParentChildValidatorTest.java +index cb8b7dd9..eea76dcf 100644 +--- a/src/test/java/net/ripe/rpki/commons/validation/X509ResourceCertificateParentChildValidatorTest.java ++++ b/src/test/java/net/ripe/rpki/commons/validation/X509ResourceCertificateParentChildValidatorTest.java +@@ -222,7 +222,7 @@ public class X509ResourceCertificateParentChildValidatorTest { + assertTrue(result.hasFailures()); + } + +- @Property ++ @Property(trials = 500) + public void validParentChildSubResources(List<@From(IpResourceGen.class) IpResource> parentResources, int childResourceCount) { + assumeThat(parentResources.size(), greaterThan(0)); + assumeThat(childResourceCount, greaterThan(0)); diff --git a/artifacts/configs/rpki-commons-500/rpki-commons-500.yaml b/artifacts/configs/rpki-commons-500/rpki-commons-500.yaml new file mode 100644 index 00000000..e402ce7a --- /dev/null +++ b/artifacts/configs/rpki-commons-500/rpki-commons-500.yaml @@ -0,0 +1,21 @@ +name: rpki-commons-500 +URL: https://github.com/RIPE-NCC/rpki-commons.git +checkoutID: dd5af7c644d2cd9cc6b0b5f4f2480b6dfc1ef074 +patchName: artifacts/configs/rpki-commons-500/rpki-commons-500.patch +mainJar: rpki-commons-DEV.jar +testJar: rpki-commons-DEV-tests.jar +#mvnOptions: -DfailIfNoTests=false +#mvnOptions: -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +properties: +# - name: RoaCMSBuilderPropertyTest#buildEncodedParseCheck +# entryPoint: net.ripe.rpki.commons.crypto.cms.roa.RoaCMSBuilderPropertyTest.buildEncodedParseCheck(JLjava/lang/Integer;)V +# - name: ManifestCMSBuilderPropertyTest#buildEncodedParseCheck +# entryPoint: net.ripe.rpki.commons.crypto.cms.manifest.ManifestCMSBuilderPropertyTest.buildEncodedParseCheck([BLjava/math/BigInteger;Ljava/lang/Integer;)V +# - name: AspaCmsTest#should_generate_aspa +# entryPoint: net.ripe.rpki.commons.crypto.cms.aspa.AspaCmsTest.should_generate_aspa(ILjava/util/List;)V + - name: X509ResourceCertificateParentChildValidatorTest#validParentChildSubResources + entryPoint: net.ripe.rpki.commons.validation.X509ResourceCertificateParentChildValidatorTest.validParentChildSubResources(Ljava/util/List;I)V +# - name: X509ResourceCertificateParentChildValidatorTest#validParentChildOverClaiming +# entryPoint: net.ripe.rpki.commons.validation.X509ResourceCertificateParentChildValidatorTest.validParentChildOverClaiming(Ljava/util/List;ILjava/util/List;)V +# - name: X509ResourceCertificateParentChildValidatorTest#validParentChildOverClaimingLooseValidation +# entryPoint: net.ripe.rpki.commons.validation.X509ResourceCertificateParentChildValidatorTest.validParentChildOverClaimingLooseValidation(Ljava/util/List;ILjava/util/List;)V diff --git a/artifacts/configs/rpki-commons-fixed/rpki-commons-fixed.patch b/artifacts/configs/rpki-commons-fixed/rpki-commons-fixed.patch new file mode 100644 index 00000000..267b1952 --- /dev/null +++ b/artifacts/configs/rpki-commons-fixed/rpki-commons-fixed.patch @@ -0,0 +1,382 @@ +diff --git a/pom.xml b/pom.xml +index 08ebb666..0157a994 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -373,6 +373,58 @@ + true + + ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ 0.8.6 ++ ++ ++ default-prepare-agent ++ ++ prepare-agent ++ ++ ++ ++ jacoco-report ++ test ++ ++ report ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-jar-plugin ++ 3.2.0 ++ ++ ++ ++ test-jar ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-assembly-plugin ++ 3.3.0 ++ ++ ++ jar-with-dependencies ++ ++ ++ ++ ++ make-assembly ++ package ++ ++ single ++ ++ ++ ++ + + + +@@ -457,4 +509,20 @@ + + + ++ ++ ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ ++ ++ ++ report ++ ++ ++ ++ ++ ++ + +diff --git a/src/test/java/net/ripe/rpki/commons/validation/X509ResourceCertificateParentChildValidatorTest.java b/src/test/java/net/ripe/rpki/commons/validation/X509ResourceCertificateParentChildValidatorTest.java +index cb8b7dd9..18f3d25a 100644 +--- a/src/test/java/net/ripe/rpki/commons/validation/X509ResourceCertificateParentChildValidatorTest.java ++++ b/src/test/java/net/ripe/rpki/commons/validation/X509ResourceCertificateParentChildValidatorTest.java +@@ -2,6 +2,9 @@ package net.ripe.rpki.commons.validation; + + import com.pholser.junit.quickcheck.From; + import com.pholser.junit.quickcheck.Property; ++import com.pholser.junit.quickcheck.generator.Also; ++import com.pholser.junit.quickcheck.generator.NullAllowed; ++import com.pholser.junit.quickcheck.generator.Size; + import com.pholser.junit.quickcheck.runner.JUnitQuickcheck; + import net.ripe.ipresource.IpResource; + import net.ripe.ipresource.IpResourceSet; +@@ -18,6 +21,7 @@ import net.ripe.rpki.commons.validation.objectvalidators.X509ResourceCertificate + import net.ripe.rpki.commons.validation.objectvalidators.X509ResourceCertificateParentChildValidator; + import net.ripe.rpki.commons.validation.objectvalidators.X509ResourceCertificateValidator; + import net.ripe.rpki.commons.validation.properties.IpResourceGen; ++import net.ripe.rpki.commons.validation.properties.URIGen; + import org.bouncycastle.asn1.x509.KeyUsage; + import org.joda.time.DateTime; + import org.junit.Before; +@@ -27,9 +31,12 @@ import org.junit.runner.RunWith; + import javax.security.auth.x500.X500Principal; + import java.math.BigInteger; + import java.net.URI; ++import java.net.URISyntaxException; + import java.security.KeyPair; + import java.util.EnumSet; + import java.util.List; ++import java.util.Objects; ++import java.util.stream.Collectors; + + import static net.ripe.rpki.commons.crypto.x509cert.X509CertificateBuilderHelper.DEFAULT_SIGNATURE_PROVIDER; + import static org.hamcrest.Matchers.greaterThan; +@@ -223,7 +230,7 @@ public class X509ResourceCertificateParentChildValidatorTest { + } + + @Property +- public void validParentChildSubResources(List<@From(IpResourceGen.class) IpResource> parentResources, int childResourceCount) { ++ public void validParentChildSubResources(List<@From(IpResourceGen.class) IpResource> parentResources, int childResourceCount, @Size(min=0, max=1000) List<@From(URIGen.class) URI> crlUris) throws URISyntaxException { + assumeThat(parentResources.size(), greaterThan(0)); + assumeThat(childResourceCount, greaterThan(0)); + +@@ -236,7 +243,7 @@ public class X509ResourceCertificateParentChildValidatorTest { + return; + } + +- ValidationResult result = validateParentChildPair(parentResourceSet, childResourceSet); ++ ValidationResult result = validateParentChildPair(parentResourceSet, childResourceSet, crlUris); + assertFalse(result.hasFailures()); + } + +@@ -305,10 +312,30 @@ public class X509ResourceCertificateParentChildValidatorTest { + return validateParentChildPairImpl(parentResourceSet, childResourceSet, false); + } + ++ private ValidationResult validateParentChildPair(IpResourceSet parentResourceSet, IpResourceSet childResourceSet, List crlUris) { ++ return validateParentChildPairImpl(parentResourceSet, childResourceSet, false, crlUris); ++ } ++ + private ValidationResult validateParentChildPairImpl(IpResourceSet parentResourceSet, IpResourceSet childResourceSet, boolean reconsidered) { +- final X509ResourceCertificate parentCertificate = createRootCertificateBuilder() +- .withResources(parentResourceSet) +- .build(); ++ return validateParentChildPairImpl(parentResourceSet, childResourceSet, reconsidered, null); ++ } ++ ++ private ValidationResult validateParentChildPairImpl(IpResourceSet parentResourceSet, IpResourceSet childResourceSet, boolean reconsidered, List crlUris) { ++ final X509ResourceCertificate parentCertificate; ++ ++ if (crlUris == null) { ++ parentCertificate = createRootCertificateBuilder() ++ .withResources(parentResourceSet) ++ .build(); ++ } else { ++ URI[] arrayUris = new URI[crlUris.size()]; ++ arrayUris = crlUris.toArray(arrayUris); ++ ++ parentCertificate = createRootCertificateBuilder() ++ .withResources(parentResourceSet) ++ .withCrlDistributionPoints(arrayUris) ++ .build(); ++ } + + final X509ResourceCertificate childCertificate = createChildCertificateBuilder() + .withResources(childResourceSet) +diff --git a/src/test/java/net/ripe/rpki/commons/validation/properties/URIGen.java b/src/test/java/net/ripe/rpki/commons/validation/properties/URIGen.java +new file mode 100644 +index 00000000..7e43902c +--- /dev/null ++++ b/src/test/java/net/ripe/rpki/commons/validation/properties/URIGen.java +@@ -0,0 +1,172 @@ ++package net.ripe.rpki.commons.validation.properties; ++ ++import com.pholser.junit.quickcheck.generator.GenerationStatus; ++import com.pholser.junit.quickcheck.generator.Generator; ++import com.pholser.junit.quickcheck.random.SourceOfRandomness; ++import java.net.URI; ++ ++public class URIGen extends Generator { ++ public final int HIER_AUTHORITY_PATH = 0; ++ public final int HIER_PATH_ABSOLUTE = 1; ++ public final int HIER_PATH_ROOTLESS = 2; ++ public final int HIER_PATH_EMPTY = 3; ++ ++ public final int HOST_REG_NAME = 0; ++ public final int HOST_IPV4 = 1; ++ public final int HOST_IPV6 = 2; ++ ++ private final String UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; ++ private final String LOWERCASE = "abcdefghijklmnopqrstuvwxyz"; ++ private final String NUMERIC = "0123456789"; ++ ++ private SourceOfRandomness r; ++ ++ public URIGen() { ++ super(URI.class); ++ } ++ ++ @Override ++ public URI generate(SourceOfRandomness r, GenerationStatus generationStatus) { ++ this.r = r; ++ ++ try { ++ return buildURI(); ++ } catch (Exception exception) { ++ // bad uri, interesting but ok or invalid options for uri generation ++ System.err.println(exception.getMessage()); ++ exception.printStackTrace(); ++ } ++ ++ return null; ++ } ++ ++ private URI buildURI() throws Exception { ++ String uri = hierPart(scheme()) + query() + fragment(); ++ return new URI(uri); ++ } ++ ++ private String hierPart(String scheme) throws Exception { ++ switch (r.nextInt(0, 3)) { ++ case HIER_AUTHORITY_PATH: return scheme + authority() + path(); ++ case HIER_PATH_ABSOLUTE: return scheme + path(); ++ case HIER_PATH_ROOTLESS: return scheme + path(""); ++ case HIER_PATH_EMPTY: return ""; ++ default: throw new Exception("Invalid option for hierPart"); ++ } ++ } ++ ++ private String scheme() { ++ String[] commonScheme = { "http", "https", "ftp", "ftps", "mailto", "file", "data", "irc", "blob", "sftp" }; ++ int pickScheme = r.nextInt(0, commonScheme.length); ++ ++ if (pickScheme == commonScheme.length) { ++ String SCHEME = UPPERCASE + LOWERCASE + NUMERIC + "+.-"; ++ return randomString(1, 1, UPPERCASE + LOWERCASE, false) ++ + randomString(2, 100, SCHEME, false) + ":"; ++ } ++ ++ return commonScheme[pickScheme] + ":"; ++ } ++ ++ private String authority() throws Exception { ++ return "//" + userinfo() + host() + port(); ++ } ++ ++ private String userinfo() { ++ if (r.nextBoolean()) { ++ return ""; ++ } ++ ++ return randomString(1, 100) + ":" + randomString(0, 100) + "@"; ++ } ++ ++ private String host() throws Exception { ++ switch (r.nextInt(0,2)) { ++ case HOST_REG_NAME: return regName(); ++ case HOST_IPV4: return ip4(); ++ case HOST_IPV6: return ipv6(); ++ default: throw new Exception("Invalid option for host"); ++ } ++ } ++ ++ private String regName() { ++ String REG = UPPERCASE + LOWERCASE + NUMERIC + ".-"; ++ return randomString(1, 255, REG); ++ } ++ ++ private String ip4() { ++ return r.nextInt(0,255) + "." + r.nextInt(0,255) + "." + r.nextInt(0,255) + "." + r.nextInt(0,255); ++ } ++ ++ private String ipv6() { ++ return String.format("%04X:%04X:%04X:%04X:%04X:%04X:%04X:%04X", ++ r.nextInt(0, 65535), r.nextInt(0, 65535), r.nextInt(0, 65535), r.nextInt(0, 65535), ++ r.nextInt(0, 65535), r.nextInt(0, 65535), r.nextInt(0, 65535), r.nextInt(0, 65535)); ++ } ++ ++ private String port() { ++ if (r.nextBoolean()) { ++ return ""; ++ } ++ ++ return ":" + r.nextInt(0, 65535); ++ } ++ ++ private String path() { ++ return path("/"); ++ } ++ ++ private String path(String append) { ++ String PATH = LOWERCASE + UPPERCASE + NUMERIC + ".+;="; ++ return append + ++ randomString(1, 1, PATH) + ++ randomString(1, 255, PATH + "/"); ++ } ++ ++ private String query() { ++ if (r.nextBoolean()) { ++ return ""; ++ } ++ ++ String QUERY = LOWERCASE + UPPERCASE + NUMERIC + "/?="; ++ return "?" + randomString(1, 255, QUERY); ++ } ++ ++ private String fragment() { ++ if (r.nextBoolean()) { ++ return ""; ++ } ++ ++ String FRAGMENT = LOWERCASE + UPPERCASE + NUMERIC + "/?="; ++ return "#" + randomString(1, 255, FRAGMENT); ++ } ++ ++ private String randomString(int minLength, int maxLength) { ++ return randomString(minLength, maxLength, LOWERCASE + UPPERCASE + NUMERIC); ++ } ++ ++ private String randomString(int minLength, int maxLength, String possibleCharacters) { ++ return randomString(minLength, maxLength, possibleCharacters, true); ++ } ++ ++ private String randomString(int minLength, int maxLength, String possibleCharacters, boolean genEncodedChars) { ++ String HEX_DIGIT = "0123456789ABCDEF"; ++ StringBuilder sb = new StringBuilder(); ++ int len = r.nextInt(minLength, maxLength); ++ ++ for (int i = 0; i < len; i++) { ++ if (genEncodedChars) { ++ int charIdx = r.nextInt(0, possibleCharacters.length()); ++ if (charIdx == possibleCharacters.length()) { ++ sb.append("%").append(randomString(2, 2, HEX_DIGIT, false)); ++ } else { ++ sb.append(possibleCharacters.charAt(charIdx)); ++ } ++ } else { ++ sb.append(possibleCharacters.charAt(r.nextInt(0, possibleCharacters.length() - 1))); ++ } ++ } ++ ++ return sb.toString(); ++ } ++} +diff --git a/src/test/java/net/ripe/rpki/commons/validation/properties/URIGenTest.java b/src/test/java/net/ripe/rpki/commons/validation/properties/URIGenTest.java +new file mode 100644 +index 00000000..e9a88133 +--- /dev/null ++++ b/src/test/java/net/ripe/rpki/commons/validation/properties/URIGenTest.java +@@ -0,0 +1,26 @@ ++package net.ripe.rpki.commons.validation.properties; ++ ++import com.pholser.junit.quickcheck.generator.GenerationStatus; ++import com.pholser.junit.quickcheck.internal.generator.SimpleGenerationStatus; ++import com.pholser.junit.quickcheck.random.SourceOfRandomness; ++import org.junit.jupiter.api.Test; ++ ++import java.net.URI; ++import java.util.Random; ++ ++import static org.junit.jupiter.api.Assertions.*; ++ ++class URIGenTest { ++ ++ @Test ++ void generateTest() { ++ URIGen uriGen = new URIGen(); ++ Random random = new Random(); ++ SourceOfRandomness r = new SourceOfRandomness(random); ++ ++ for (int i = 0; i < 100_000; i++) { ++ URI uri = uriGen.generate(r, null); ++ assertNotNull(uri); ++ } ++ } ++} +\ No newline at end of file diff --git a/artifacts/configs/rpki-commons-fixed/rpki-commons-fixed.yaml b/artifacts/configs/rpki-commons-fixed/rpki-commons-fixed.yaml new file mode 100644 index 00000000..8e42e232 --- /dev/null +++ b/artifacts/configs/rpki-commons-fixed/rpki-commons-fixed.yaml @@ -0,0 +1,21 @@ +name: rpki-commons-fixed +URL: https://github.com/RIPE-NCC/rpki-commons.git +checkoutID: dd5af7c644d2cd9cc6b0b5f4f2480b6dfc1ef074 +patchName: artifacts/configs/rpki-commons-fixed/rpki-commons-fixed.patch +mainJar: rpki-commons-DEV.jar +testJar: rpki-commons-DEV-tests.jar +#mvnOptions: -DfailIfNoTests=false +#mvnOptions: -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +properties: +# - name: RoaCMSBuilderPropertyTest#buildEncodedParseCheck +# entryPoint: net.ripe.rpki.commons.crypto.cms.roa.RoaCMSBuilderPropertyTest.buildEncodedParseCheck(JLjava/lang/Integer;)V +# - name: ManifestCMSBuilderPropertyTest#buildEncodedParseCheck +# entryPoint: net.ripe.rpki.commons.crypto.cms.manifest.ManifestCMSBuilderPropertyTest.buildEncodedParseCheck([BLjava/math/BigInteger;Ljava/lang/Integer;)V +# - name: AspaCmsTest#should_generate_aspa +# entryPoint: net.ripe.rpki.commons.crypto.cms.aspa.AspaCmsTest.should_generate_aspa(ILjava/util/List;)V + - name: X509ResourceCertificateParentChildValidatorTest#validParentChildSubResources + entryPoint: net.ripe.rpki.commons.validation.X509ResourceCertificateParentChildValidatorTest.validParentChildSubResources(Ljava/util/List;ILjava/util/List;)V +# - name: X509ResourceCertificateParentChildValidatorTest#validParentChildOverClaiming +# entryPoint: net.ripe.rpki.commons.validation.X509ResourceCertificateParentChildValidatorTest.validParentChildOverClaiming(Ljava/util/List;ILjava/util/List;)V +# - name: X509ResourceCertificateParentChildValidatorTest#validParentChildOverClaimingLooseValidation +# entryPoint: net.ripe.rpki.commons.validation.X509ResourceCertificateParentChildValidatorTest.validParentChildOverClaimingLooseValidation(Ljava/util/List;ILjava/util/List;)V diff --git a/artifacts/configs/rpki-commons/rpki-commons.patch b/artifacts/configs/rpki-commons/rpki-commons.patch new file mode 100644 index 00000000..1638a53a --- /dev/null +++ b/artifacts/configs/rpki-commons/rpki-commons.patch @@ -0,0 +1,84 @@ +diff --git a/pom.xml b/pom.xml +index 08ebb666..0157a994 100644 +--- a/pom.xml ++++ b/pom.xml +@@ -373,6 +373,58 @@ + true + + ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ 0.8.6 ++ ++ ++ default-prepare-agent ++ ++ prepare-agent ++ ++ ++ ++ jacoco-report ++ test ++ ++ report ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-jar-plugin ++ 3.2.0 ++ ++ ++ ++ test-jar ++ ++ ++ ++ ++ ++ org.apache.maven.plugins ++ maven-assembly-plugin ++ 3.3.0 ++ ++ ++ jar-with-dependencies ++ ++ ++ ++ ++ make-assembly ++ package ++ ++ single ++ ++ ++ ++ + + + +@@ -457,4 +509,20 @@ + + + ++ ++ ++ ++ ++ org.jacoco ++ jacoco-maven-plugin ++ ++ ++ ++ report ++ ++ ++ ++ ++ ++ + diff --git a/artifacts/configs/rpki-commons/rpki-commons.yaml b/artifacts/configs/rpki-commons/rpki-commons.yaml new file mode 100644 index 00000000..851e48a3 --- /dev/null +++ b/artifacts/configs/rpki-commons/rpki-commons.yaml @@ -0,0 +1,21 @@ +name: rpki-commons +URL: https://github.com/RIPE-NCC/rpki-commons.git +checkoutID: dd5af7c644d2cd9cc6b0b5f4f2480b6dfc1ef074 +patchName: artifacts/configs/rpki-commons/rpki-commons.patch +mainJar: rpki-commons-DEV.jar +testJar: rpki-commons-DEV-tests.jar +#mvnOptions: -DfailIfNoTests=false +#mvnOptions: -Dmaven.surefire.debug="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000" +properties: +# - name: RoaCMSBuilderPropertyTest#buildEncodedParseCheck +# entryPoint: net.ripe.rpki.commons.crypto.cms.roa.RoaCMSBuilderPropertyTest.buildEncodedParseCheck(JLjava/lang/Integer;)V +# - name: ManifestCMSBuilderPropertyTest#buildEncodedParseCheck +# entryPoint: net.ripe.rpki.commons.crypto.cms.manifest.ManifestCMSBuilderPropertyTest.buildEncodedParseCheck([BLjava/math/BigInteger;Ljava/lang/Integer;)V +# - name: AspaCmsTest#should_generate_aspa +# entryPoint: net.ripe.rpki.commons.crypto.cms.aspa.AspaCmsTest.should_generate_aspa(ILjava/util/List;)V + - name: X509ResourceCertificateParentChildValidatorTest#validParentChildSubResources + entryPoint: net.ripe.rpki.commons.validation.X509ResourceCertificateParentChildValidatorTest.validParentChildSubResources(Ljava/util/List;I)V +# - name: X509ResourceCertificateParentChildValidatorTest#validParentChildOverClaiming +# entryPoint: net.ripe.rpki.commons.validation.X509ResourceCertificateParentChildValidatorTest.validParentChildOverClaiming(Ljava/util/List;ILjava/util/List;)V +# - name: X509ResourceCertificateParentChildValidatorTest#validParentChildOverClaimingLooseValidation +# entryPoint: net.ripe.rpki.commons.validation.X509ResourceCertificateParentChildValidatorTest.validParentChildOverClaimingLooseValidation(Ljava/util/List;ILjava/util/List;)V diff --git a/artifacts/expected/convex/GenTestFormat#dataRoundTrip-reachability.png b/artifacts/expected/convex/GenTestFormat#dataRoundTrip-reachability.png new file mode 100644 index 00000000..74d53f68 Binary files /dev/null and b/artifacts/expected/convex/GenTestFormat#dataRoundTrip-reachability.png differ diff --git a/artifacts/expected/convex/GenTestFormat#messageRoundTrip-reachability.png b/artifacts/expected/convex/GenTestFormat#messageRoundTrip-reachability.png new file mode 100644 index 00000000..cbde2fe8 Binary files /dev/null and b/artifacts/expected/convex/GenTestFormat#messageRoundTrip-reachability.png differ diff --git a/artifacts/expected/convex/GenTestFormat#primitiveRoundTrip-reachability.png b/artifacts/expected/convex/GenTestFormat#primitiveRoundTrip-reachability.png new file mode 100644 index 00000000..3078c931 Binary files /dev/null and b/artifacts/expected/convex/GenTestFormat#primitiveRoundTrip-reachability.png differ diff --git a/artifacts/expected/jflex/CharClassesQuickcheck#addSet-reachability.png b/artifacts/expected/jflex/CharClassesQuickcheck#addSet-reachability.png new file mode 100644 index 00000000..de618dd4 Binary files /dev/null and b/artifacts/expected/jflex/CharClassesQuickcheck#addSet-reachability.png differ diff --git a/artifacts/expected/jflex/CharClassesQuickcheck#addSetComplement-reachability.png b/artifacts/expected/jflex/CharClassesQuickcheck#addSetComplement-reachability.png new file mode 100644 index 00000000..271bcff0 Binary files /dev/null and b/artifacts/expected/jflex/CharClassesQuickcheck#addSetComplement-reachability.png differ diff --git a/artifacts/expected/jflex/CharClassesQuickcheck#addSetParts-reachability.png b/artifacts/expected/jflex/CharClassesQuickcheck#addSetParts-reachability.png new file mode 100644 index 00000000..b480fc5a Binary files /dev/null and b/artifacts/expected/jflex/CharClassesQuickcheck#addSetParts-reachability.png differ diff --git a/artifacts/expected/jflex/CharClassesQuickcheck#addSingle-reachability.png b/artifacts/expected/jflex/CharClassesQuickcheck#addSingle-reachability.png new file mode 100644 index 00000000..2e8ae038 Binary files /dev/null and b/artifacts/expected/jflex/CharClassesQuickcheck#addSingle-reachability.png differ diff --git a/artifacts/expected/jflex/CharClassesQuickcheck#addSingleSingleton-reachability.png b/artifacts/expected/jflex/CharClassesQuickcheck#addSingleSingleton-reachability.png new file mode 100644 index 00000000..7dd0a85f Binary files /dev/null and b/artifacts/expected/jflex/CharClassesQuickcheck#addSingleSingleton-reachability.png differ diff --git a/artifacts/expected/jflex/CharClassesQuickcheck#addString-reachability.png b/artifacts/expected/jflex/CharClassesQuickcheck#addString-reachability.png new file mode 100644 index 00000000..61df20bf Binary files /dev/null and b/artifacts/expected/jflex/CharClassesQuickcheck#addString-reachability.png differ diff --git a/artifacts/expected/jflex/CharClassesQuickcheck#classCodesCode-reachability.png b/artifacts/expected/jflex/CharClassesQuickcheck#classCodesCode-reachability.png new file mode 100644 index 00000000..58675a3c Binary files /dev/null and b/artifacts/expected/jflex/CharClassesQuickcheck#classCodesCode-reachability.png differ diff --git a/artifacts/expected/jflex/CharClassesQuickcheck#classCodesDisjointOrdered-reachability.png b/artifacts/expected/jflex/CharClassesQuickcheck#classCodesDisjointOrdered-reachability.png new file mode 100644 index 00000000..6ca85cce Binary files /dev/null and b/artifacts/expected/jflex/CharClassesQuickcheck#classCodesDisjointOrdered-reachability.png differ diff --git a/artifacts/expected/jflex/CharClassesQuickcheck#classCodesUnion-reachability.png b/artifacts/expected/jflex/CharClassesQuickcheck#classCodesUnion-reachability.png new file mode 100644 index 00000000..daf56cd9 Binary files /dev/null and b/artifacts/expected/jflex/CharClassesQuickcheck#classCodesUnion-reachability.png differ diff --git a/artifacts/expected/jflex/CharClassesQuickcheck#computeTablesEq-reachability.png b/artifacts/expected/jflex/CharClassesQuickcheck#computeTablesEq-reachability.png new file mode 100644 index 00000000..640de76b Binary files /dev/null and b/artifacts/expected/jflex/CharClassesQuickcheck#computeTablesEq-reachability.png differ diff --git a/artifacts/expected/jflex/CharClassesQuickcheck#getTablesEq-reachability.png b/artifacts/expected/jflex/CharClassesQuickcheck#getTablesEq-reachability.png new file mode 100644 index 00000000..54409e0e Binary files /dev/null and b/artifacts/expected/jflex/CharClassesQuickcheck#getTablesEq-reachability.png differ diff --git a/artifacts/expected/jflex/CharClassesQuickcheck#invariants-reachability.png b/artifacts/expected/jflex/CharClassesQuickcheck#invariants-reachability.png new file mode 100644 index 00000000..206a4479 Binary files /dev/null and b/artifacts/expected/jflex/CharClassesQuickcheck#invariants-reachability.png differ diff --git a/artifacts/expected/jflex/CharClassesQuickcheck#maxCharCode-reachability.png b/artifacts/expected/jflex/CharClassesQuickcheck#maxCharCode-reachability.png new file mode 100644 index 00000000..79791569 Binary files /dev/null and b/artifacts/expected/jflex/CharClassesQuickcheck#maxCharCode-reachability.png differ diff --git a/artifacts/expected/jflex/CharClassesQuickcheck#normaliseSingle-reachability.png b/artifacts/expected/jflex/CharClassesQuickcheck#normaliseSingle-reachability.png new file mode 100644 index 00000000..a01ba46a Binary files /dev/null and b/artifacts/expected/jflex/CharClassesQuickcheck#normaliseSingle-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#addCommutes-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#addCommutes-reachability.png new file mode 100644 index 00000000..d8109b68 Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#addCommutes-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#addEmpty-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#addEmpty-reachability.png new file mode 100644 index 00000000..d6656728 Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#addEmpty-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#addIdemPotent-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#addIdemPotent-reachability.png new file mode 100644 index 00000000..2fe78bda Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#addIdemPotent-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#addIsUnion-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#addIsUnion-reachability.png new file mode 100644 index 00000000..bd320219 Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#addIsUnion-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#addSelf-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#addSelf-reachability.png new file mode 100644 index 00000000..ae2dd9fe Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#addSelf-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#addStateAdd-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#addStateAdd-reachability.png new file mode 100644 index 00000000..963a73fa Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#addStateAdd-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#addStateAdds-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#addStateAdds-reachability.png new file mode 100644 index 00000000..b101094b Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#addStateAdds-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#addStateDoesNotRemove-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#addStateDoesNotRemove-reachability.png new file mode 100644 index 00000000..5b52ca36 Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#addStateDoesNotRemove-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#clearMakesEmpty-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#clearMakesEmpty-reachability.png new file mode 100644 index 00000000..a1b78cee Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#clearMakesEmpty-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#complementElements-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#complementElements-reachability.png new file mode 100644 index 00000000..b479f8b9 Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#complementElements-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#complementNoOriginalElements-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#complementNoOriginalElements-reachability.png new file mode 100644 index 00000000..a1684d6a Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#complementNoOriginalElements-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#complementUnion-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#complementUnion-reachability.png new file mode 100644 index 00000000..79c7eedb Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#complementUnion-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#containsElements-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#containsElements-reachability.png new file mode 100644 index 00000000..19b88034 Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#containsElements-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#containsIsSubset-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#containsIsSubset-reachability.png new file mode 100644 index 00000000..c5371a2d Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#containsIsSubset-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#containsItsElements-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#containsItsElements-reachability.png new file mode 100644 index 00000000..d68b00c2 Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#containsItsElements-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#containsNoElements-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#containsNoElements-reachability.png new file mode 100644 index 00000000..ca1691ca Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#containsNoElements-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#copy-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#copy-reachability.png new file mode 100644 index 00000000..d5bd0804 Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#copy-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#copyInto-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#copyInto-reachability.png new file mode 100644 index 00000000..721f0f79 Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#copyInto-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#enumerator-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#enumerator-reachability.png new file mode 100644 index 00000000..d6ccd187 Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#enumerator-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#getAndRemoveAdd-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#getAndRemoveAdd-reachability.png new file mode 100644 index 00000000..034797bc Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#getAndRemoveAdd-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#getAndRemoveIsElement-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#getAndRemoveIsElement-reachability.png new file mode 100644 index 00000000..bea7285d Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#getAndRemoveIsElement-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#getAndRemoveRemoves-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#getAndRemoveRemoves-reachability.png new file mode 100644 index 00000000..f9607fff Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#getAndRemoveRemoves-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#hashCode-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#hashCode-reachability.png new file mode 100644 index 00000000..3e4a1cf2 Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#hashCode-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#intersect-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#intersect-reachability.png new file mode 100644 index 00000000..32915c77 Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#intersect-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#intersectCommutes-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#intersectCommutes-reachability.png new file mode 100644 index 00000000..49c85212 Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#intersectCommutes-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#intersectEmpty-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#intersectEmpty-reachability.png new file mode 100644 index 00000000..4b04bc9c Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#intersectEmpty-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#intersectSelf-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#intersectSelf-reachability.png new file mode 100644 index 00000000..3966e17d Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#intersectSelf-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#intersectUnchanged-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#intersectUnchanged-reachability.png new file mode 100644 index 00000000..24960fc8 Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#intersectUnchanged-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#removeAdd-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#removeAdd-reachability.png new file mode 100644 index 00000000..23438e2e Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#removeAdd-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#removeRemoves-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#removeRemoves-reachability.png new file mode 100644 index 00000000..243049d6 Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#removeRemoves-reachability.png differ diff --git a/artifacts/expected/jflex/StateSetQuickcheck#size2nbits-reachability.png b/artifacts/expected/jflex/StateSetQuickcheck#size2nbits-reachability.png new file mode 100644 index 00000000..c8ed96cc Binary files /dev/null and b/artifacts/expected/jflex/StateSetQuickcheck#size2nbits-reachability.png differ diff --git a/artifacts/expected/mph-table/TestSmartByteSerializer#canRoundTripBytes-reachability.png b/artifacts/expected/mph-table/TestSmartByteSerializer#canRoundTripBytes-reachability.png new file mode 100644 index 00000000..acfe8541 Binary files /dev/null and b/artifacts/expected/mph-table/TestSmartByteSerializer#canRoundTripBytes-reachability.png differ diff --git a/artifacts/expected/mph-table/TestSmartIntegerSerializer#canRoundTripIntegers-reachability.png b/artifacts/expected/mph-table/TestSmartIntegerSerializer#canRoundTripIntegers-reachability.png new file mode 100644 index 00000000..f0de5478 Binary files /dev/null and b/artifacts/expected/mph-table/TestSmartIntegerSerializer#canRoundTripIntegers-reachability.png differ diff --git a/artifacts/expected/mph-table/TestSmartListSerializer#canRoundTripSerializableLists-reachability.png b/artifacts/expected/mph-table/TestSmartListSerializer#canRoundTripSerializableLists-reachability.png new file mode 100644 index 00000000..3aa54799 Binary files /dev/null and b/artifacts/expected/mph-table/TestSmartListSerializer#canRoundTripSerializableLists-reachability.png differ diff --git a/artifacts/expected/mph-table/TestSmartLongSerializer#canRoundTripLongs-reachability.png b/artifacts/expected/mph-table/TestSmartLongSerializer#canRoundTripLongs-reachability.png new file mode 100644 index 00000000..caa6960c Binary files /dev/null and b/artifacts/expected/mph-table/TestSmartLongSerializer#canRoundTripLongs-reachability.png differ diff --git a/artifacts/expected/mph-table/TestSmartOptionalSerializer#canRoundTripPresentOptionals-reachability.png b/artifacts/expected/mph-table/TestSmartOptionalSerializer#canRoundTripPresentOptionals-reachability.png new file mode 100644 index 00000000..44767934 Binary files /dev/null and b/artifacts/expected/mph-table/TestSmartOptionalSerializer#canRoundTripPresentOptionals-reachability.png differ diff --git a/artifacts/expected/mph-table/TestSmartPairSerializer#canRoundTripPairs-reachability.png b/artifacts/expected/mph-table/TestSmartPairSerializer#canRoundTripPairs-reachability.png new file mode 100644 index 00000000..e65d1383 Binary files /dev/null and b/artifacts/expected/mph-table/TestSmartPairSerializer#canRoundTripPairs-reachability.png differ diff --git a/artifacts/expected/mph-table/TestSmartShortSerializer#canRoundTripShort-reachability.png b/artifacts/expected/mph-table/TestSmartShortSerializer#canRoundTripShort-reachability.png new file mode 100644 index 00000000..a02aaa57 Binary files /dev/null and b/artifacts/expected/mph-table/TestSmartShortSerializer#canRoundTripShort-reachability.png differ diff --git a/artifacts/expected/mph-table/TestSmartStringSerializer#canRoundTripStrings-reachability.png b/artifacts/expected/mph-table/TestSmartStringSerializer#canRoundTripStrings-reachability.png new file mode 100644 index 00000000..b3ba63ba Binary files /dev/null and b/artifacts/expected/mph-table/TestSmartStringSerializer#canRoundTripStrings-reachability.png differ diff --git a/artifacts/experiment/.gitkeep b/artifacts/experiment/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/artifacts/experiments/RQ2/build_mph.sh b/artifacts/experiments/RQ2/build_mph.sh new file mode 100644 index 00000000..40920340 --- /dev/null +++ b/artifacts/experiments/RQ2/build_mph.sh @@ -0,0 +1,11 @@ +java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c mph-table +java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c mph-table-10 +java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c mph-table-50 +java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c mph-table-500 +java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c mph-table-1000 + +java -jar ./target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c mph-table -o mph-table_graph +java -jar ./target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c mph-table-10 -o mph-table-10_graph +java -jar ./target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c mph-table-50 -o mph-table-50_graph +java -jar ./target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c mph-table-500 -o mph-table-500_graph +java -jar ./target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c mph-table-1000 -o mph-table-1000_graph diff --git a/artifacts/experiments/RQ2/build_trials.sh b/artifacts/experiments/RQ2/build_trials.sh new file mode 100644 index 00000000..abb81fa0 --- /dev/null +++ b/artifacts/experiments/RQ2/build_trials.sh @@ -0,0 +1,36 @@ +java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c mph-table-10 +java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c mph-table-50 +java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c mph-table-500 +java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c mph-table-1000 +java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c convex-10 +java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c convex-50 +java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c convex-500 +java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c convex-1000 +java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c jflex-10 +java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c jflex-50 +java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c jflex-500 +java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c jflex-1000 +java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c rpki-commons-10 +java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c rpki-commons-50 +java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c rpki-commons-500 +java -jar target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c rpki-commons-1000 + +java -jar ./target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c mph-table-10 -o mph-table-10_graph +java -jar ./target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c mph-table-50 -o mph-table-50_graph +java -jar ./target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c mph-table-500 -o mph-table-500_graph +java -jar ./target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c mph-table-1000 -o mph-table-1000_graph + +java -jar ./target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c convex-10 -o convex-10_graph +java -jar ./target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c convex-50 -o convex-50_graph +java -jar ./target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c convex-500 -o convex-500_graph +java -jar ./target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c convex-1000 -o convex-1000_graph + +java -jar ./target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c jflex-10 -o jflex-10_graph +java -jar ./target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c jflex-50 -o jflex-50_graph +java -jar ./target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c jflex-500 -o jflex-500_graph +java -jar ./target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c jflex-1000 -o jflex-1000_graph + +java -jar ./target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c rpki-commons-10 -o rpki-commons-10_graph +java -jar ./target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c rpki-commons-50 -o rpki-commons-50_graph +java -jar ./target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c rpki-commons-500 -o rpki-commons-500_graph +java -jar ./target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c rpki-commons-1000 -o rpki-commons-1000_graph diff --git a/artifacts/experiments/RQ2/generateResults.py b/artifacts/experiments/RQ2/generateResults.py new file mode 100644 index 00000000..dc2e3e16 --- /dev/null +++ b/artifacts/experiments/RQ2/generateResults.py @@ -0,0 +1,208 @@ +import datetime +import os +import pandas as pd +import numpy as np +import re + +BASE_RESULT_DIR = "artifacts/results/" +PROJECTS = ["convex", "jflex", "mph-table", "rpki-commons"] +REPORT_NAME = "artifacts/output/rq2.csv" +TEX_REPORT_NAME = "artifacts/output/rq2.tex" +ITERATIONS = [10, 50, 500, 1000] +RAW_NAMES = ["Property", "10", "50", "100", "500", "1000"] +row_count = 1 + +propertyShortNames = { + "TestSmartListSerializer#canRoundTripSerializableLists": 'list', + "GenTestFormat#dataRoundTrip": 'data', + "GenTestFormat#messageRoundTrip": 'message', + "GenTestFormat#primitiveRoundTrip": 'primitive', + "CharClassesQuickcheck#addSet": 'addSet', + "CharClassesQuickcheck#addSingle": 'addSingle', + "CharClassesQuickcheck#addSingleSingleton": 'addSingleton', + "CharClassesQuickcheck#addString": 'addString', + "StateSetQuickcheck#addStateDoesNotRemove": 'add', + "StateSetQuickcheck#containsElements": 'contains', + "StateSetQuickcheck#removeAdd": 'remove', + "X509ResourceCertificateParentChildValidatorTest#validParentChildSubResources": 'resources' +} + + +def obtain_stats_directories(results_directory: str) -> list[str]: + directory_tree = [x for x in os.walk( + results_directory)] # os.walk returns a tuple with structure (directory, subdirectories, files) + return directory_tree[0][1] + + +def filter_for_recent_results(project_name: str, stats_directories: list[str]) -> list[str]: + if "convex" in project_name: + project_string = project_name.split("-")[0] + "-core" + elif "jflex" in project_name: + project_string = "jflex" + else: + project_string = project_name + + time_stamps = [datetime.datetime.strptime(x.replace(project_string, "").replace("_", ":").replace("T", " "), + "%Y-%m-%d %H:%M:%S.%f") + for x in stats_directories] + time_stamps.sort() + valid_runs = time_stamps[-10:] + valid_directories = [] + for directory in stats_directories: + val = datetime.datetime.strptime(directory.replace(project_string, "").replace("_", ":").replace("T", " "), + "%Y-%m-%d %H:%M:%S.%f") + if val in valid_runs: + valid_directories.append(directory) + return valid_directories + +def calculate_coverage(file: str) -> int: + coverage: int = 0 + with open(file) as f: + lines = [line.rstrip() for line in f] + # nodes_covered = int(lines[1].replace("nodesCovered,", "")) + # node_count = int(lines[2].replace("nodeCount,", "")) + lines_covered = int(lines[3].replace("linesCovered,", "")) + # lines_missed = int(lines[4].replace("linesMissed,", "")) + + coverage = lines_covered + # coverage["LC"] = lines_covered / (lines_covered + lines_missed) * 100 + return coverage + + +def obtain_time_elapsed(time_file: str) -> float: + with open(time_file) as f: + contents = f.read() + time_elapsed_regrex = re.search('Total Time Elapsed: (.+?) seconds', contents) + if time_elapsed_regrex: + time_elapsed = time_elapsed_regrex.group(1) + return round(float(time_elapsed), 2) + return -1.00 + + +def obtain_iteration_stats(iteration_directories: list[str]) -> dict[str, tuple]: + stats: dict[str, list] = {} + times: dict[str, list] = {} + for iteration_directory in iteration_directories: + files = [x for x in os.walk( + iteration_directory)][0][2] + stats_files = list(filter(lambda stat_file: "reachability-coverage.csv" in stat_file, files)) + time_files = [f.replace("-reachability-coverage.csv", ".html") for f in stats_files] + for file, time_file in zip(stats_files, time_files): + file_location = iteration_directory + "/" + file + time_file_location = iteration_directory + "/" + time_file + prop = file.replace("-reachability-coverage.csv", "") + if prop not in stats: + stats[prop] = [] + times[prop] = [] + stats[prop].append(calculate_coverage(file=file_location)) + times[prop].append(obtain_time_elapsed(time_file=time_file_location)) + ret = {} + for key, val in stats.items(): + np_array_stats = np.array(val) + mean_stats = '{:.2f}'.format(round(np_array_stats.mean(), 2)) + standard_dev_stats = '{:.2f}'.format(round(np_array_stats.std(), 2)) + time_val = times[key] + np_array_times = np.array(time_val) + mean_times = '{:.2f}'.format(round(np_array_times.mean(), 2)) + standard_dev_times = '{:.2f}'.format(round(np_array_times.std(), 2)) + + stats_str = str(mean_stats) + " \u00B1 " + str(standard_dev_stats) + times_str = str(mean_times) + " \u00B1 " + str(standard_dev_times) + ret[key] = (stats_str, times_str) + return ret + + +def generate_project_df(project_ds: dict[int, dict], row_count: int) -> pd.DataFrame(): + property_dict = {} + valid_keys = project_ds[10].keys() # grab first dict keys for property names + for key in valid_keys: + property_dict[key] = [] + for trial in project_ds.keys(): + if key not in project_ds[trial]: + property_dict[key].append((np.nan, np.nan)) + else: + property_dict[key].append(project_ds[trial][key]) + + project_df = pd.DataFrame() + for prop, val in property_dict.items(): + print(val) + mc_dict = {"N": row_count, "Property": propertyShortNames[prop], 10: val[1][0], + 50: val[2][0], 100: val[0][0], 500: val[3][0], 1000: val[4][0]} + row_count += 1 + tt_dict = {"N": row_count, "Property": "time(s)", 10: val[1][1], + 50: val[2][1], 100: val[0][1], 500: val[3][1], 1000: val[4][1]} + method_coverage_df = pd.DataFrame(mc_dict, index=[i for i in range(1)]) + time_taken_df = pd.DataFrame(tt_dict, index=[i for i in range(1)]) + + project_df = pd.concat([project_df, method_coverage_df, time_taken_df]) + return project_df + + +def main(): + final_dataset = {} + for project in PROJECTS: + project_dataset = {} + stats_directory_base = BASE_RESULT_DIR + project + "/" + project_base_iteration_stats = obtain_stats_directories(results_directory=stats_directory_base) + filtered_results_base = filter_for_recent_results(project_name=project, + stats_directories=project_base_iteration_stats) + iteration_directories_base = [stats_directory_base + result for result in filtered_results_base] + iteration_stats = obtain_iteration_stats(iteration_directories=iteration_directories_base) + project_dataset[100] = iteration_stats + for iteration in ITERATIONS: + project_name = project + "-" + str(iteration) + stats_directory = BASE_RESULT_DIR + project_name + "/" + project_iteration_stats = obtain_stats_directories(results_directory=stats_directory) + filtered_results = filter_for_recent_results(project_name=project_name, + stats_directories=project_iteration_stats) + iteration_directories = [stats_directory + result for result in filtered_results] + iteration_stats = obtain_iteration_stats(iteration_directories=iteration_directories) + project_dataset[iteration] = iteration_stats + final_dataset[project] = generate_project_df(project_ds=project_dataset, row_count=row_count) + print(final_dataset) + + with open(TEX_REPORT_NAME, 'w') as tf: + df = pd.DataFrame() + for project in PROJECTS: + final_dataset[project]['_style'] = '' + header = dict(zip(['N', 'Property', 10, 50, 100, 500, 1000], ['', '', '', '', '', '', ''])) + final_dataset[project]['N'] = pd.RangeIndex(start=row_count, + stop=len(final_dataset[project].index) + row_count) + df = pd.concat([ + df, + pd.DataFrame(header | {'_style': 'HEADER', 'Property': project}, index=[0]), + final_dataset[project] + ], ignore_index=True) + + bold_rows = df[df['_style'] == 'BOLD'].index + header_rows = df[df['_style'] == 'HEADER'].index + latexTable = df \ + .drop(columns=['_style']) \ + .style \ + .hide(axis=0) \ + .format(precision=2) \ + .set_properties(subset=pd.IndexSlice[header_rows, :], **{'HEADER': ''}) \ + .set_properties(subset=pd.IndexSlice[bold_rows, :], **{'textbf': '--rwrap'}) \ + .to_latex(hrules=False) + + outTable = '' + + # transform to sub headers + for line in latexTable.splitlines(keepends=True): + s = line.split('&') + c = str(len(s)) + + possibleCommand = s[0].strip() + + if possibleCommand == '\HEADER': + outTable += '\\hline' + "\n" + '\multicolumn{' + c + '}{c}{\\' + s[1].strip()[ + 7:].strip().replace("-", + "") + '}' + " \\\\\n" + '\\hline' + "\n" + else: + outTable += line.replace("nan", "-") + + tf.write(outTable) + + +if __name__ == "__main__": + main() diff --git a/artifacts/experiments/RQ2/generateStats.py b/artifacts/experiments/RQ2/generateStats.py new file mode 100755 index 00000000..70327848 --- /dev/null +++ b/artifacts/experiments/RQ2/generateStats.py @@ -0,0 +1,42 @@ +import glob +import os +import subprocess + +JAR_FILE = "target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar" + +PROJECTS = ["convex", "jflex", "mph-table", "rpki-commons"] +TRIALS = [10, 50, 500, 1000] + + +def test_properties(project_name: str): + project_graph = project_name + "_graph" + subprocess.run(["java", "-jar", JAR_FILE, "test", "-c", + project_name, "-f", project_graph]) + + +def test_properties_with_trials(project_name: str, trials: int): + project_name_with_trials = project_name + "-" + str(trials) + project_graph = project_name_with_trials + "_graph" + subprocess.run(["java", "-jar", JAR_FILE, "test", "-c", + project_name_with_trials, "-f", project_graph]) + + +def clear_output(): + files = glob.glob('output/*') + for f in files: + os.remove(f) + + +def main(): + for project in PROJECTS: + for index in range(10): + test_properties(project_name=project) + clear_output() + for trial in TRIALS: + for index in range(10): + test_properties_with_trials(project_name=project, trials=trial) + clear_output() + + +if __name__ == "__main__": + main() diff --git a/artifacts/experiments/RQ4/generateResults.py b/artifacts/experiments/RQ4/generateResults.py new file mode 100755 index 00000000..dcedbb03 --- /dev/null +++ b/artifacts/experiments/RQ4/generateResults.py @@ -0,0 +1,261 @@ +import datetime +import os +import re + +import numpy as np +import pandas as pd + +BASE_RESULT_DIR = "artifacts/results/" +PROJECTS = ["convex", "jflex", "mph-table", "rpki-commons"] +REPORT_NAME = "artifacts/output/rq4.csv" +TEX_REPORT_NAME = "artifacts/output/rq4.tex" + +RAW_NAMES = ['Vanilla', 'Improved'] +CALC_NAMES = ['Vanilla', 'Improved', 'Overhead'] + +propertyShortNames = { + "TestSmartListSerializer#canRoundTripSerializableLists": 'list', + "TestSmartListSerializer#canRoundTripSerializableListsNaive": 'naive', + "TestSmartListSerializer#canRoundTripSerializableListsWithGenerator": 'list*', + "GenTestFormat#dataRoundTrip": 'data', + "GenTestFormat#messageRoundTrip": 'message', + "GenTestFormat#primitiveRoundTrip": 'primitive', + "CharClassesQuickcheck#addSet": 'addSet', + "CharClassesQuickcheck#addSingle": 'addSingle', + "CharClassesQuickcheck#addSingleSingleton": 'addSingleton', + "CharClassesQuickcheck#addString": 'addString', + "StateSetQuickcheck#addStateDoesNotRemove": 'add', + "StateSetQuickcheck#containsElements": 'contains', + "StateSetQuickcheck#removeAdd": 'remove', + "X509ResourceCertificateParentChildValidatorTest#validParentChildSubResources": 'resources' +} + + +def obtain_stats_directories(results_directory: str) -> list[str]: + directory_tree = [x for x in os.walk(results_directory)] # os.walk returns a tuple with structure (directory, subdirectories, files) + return directory_tree[0][1] + +def filter_for_recent_results(project_name: str, stats_directories: list[str]) -> dict[str, str]: + valid_directories = [] + project_string = project_name if project_name != "convex" else project_name + "-core" # edge case + if "mph-table-fixed" in stats_directories[0]: # edge case + project_string = "mph-table-fixed" + elif "mph-table-naive" in stats_directories[0]: + project_string = "mph-table-naive" + elif "rpki-commons-fixed" in stats_directories[0]: + project_string = "rpki-commons-fixed" + time_stamps = [datetime.datetime.strptime(x.replace(project_string, "").replace("_", ":").replace("T", " "), "%Y-%m-%d %H:%M:%S.%f") + for x in stats_directories] + time_stamps.sort() + valid_runs = time_stamps[-10:] + for directory in stats_directories: + val = datetime.datetime.strptime(directory.replace(project_string, "").replace("_", ":").replace("T", " "), "%Y-%m-%d %H:%M:%S.%f") + if val in valid_runs: + valid_directories.append(directory) + return valid_directories + +def evaluate_directories(project_name: str, results_directory: str, directories: list[str])-> dict[str, dict]: + final_stats = {} + iteration = 1 + for directory in directories: + directory_path = results_directory + directory + "/" + directory_tree = [x[2] for x in os.walk(directory_path)] + valid_htmls = [x for x in directory_tree[0] if 'html' in x] + directory_stats = retrieve_time_elapsed(project_name=project_name, directory_path=directory_path, valid_htmls=valid_htmls) + project_iteration = project_name + " - " + str(iteration) + final_stats[project_iteration] = directory_stats + iteration += 1 + return final_stats + + +def retrieve_time_elapsed(project_name: str, directory_path: str, valid_htmls: list[str]) -> dict[str, str]: + times_elapsed_dict = {} + for html_file in valid_htmls: + property_name = html_file.replace(".html", "") + if property_name not in propertyShortNames: + continue + property_short_name = propertyShortNames[property_name] + if property_short_name == 'list*' and project_name == 'mph-table-fixed': + property_short_name = 'list' + elif property_short_name == 'list' and project_name == 'mph-table-fixed': + continue + elif property_short_name == 'list*': + print(project_name) + continue + file_path = directory_path + html_file + with open(file_path) as f: + contents = f.read() + time_elapsed_regrex = re.search('Total Time Elapsed: (.+?) seconds', contents) + if time_elapsed_regrex: + time_elapsed = time_elapsed_regrex.group(1) + times_elapsed_dict[property_short_name] = round(float(time_elapsed), 2) + return times_elapsed_dict + +def generate_report_stats(stat_values: dict[str, dict]) -> dict[str, str]: + first_iteration = stat_values[next(iter(stat_values))] + # stage a dictionary to contain an array of times for ea property + property_dict = {} + for key in first_iteration: + property_dict[key] = [] + + # populate the dictionary with our results + for key, val in stat_values.items(): + for prop, time in val.items(): + property_array = property_dict.get(prop) + if property_array is None: + property_dict[prop] = [] + property_array = property_dict.get(prop) + property_array.append(time) + + # generate mean, standard deviation and populate our final object + property_stats_dict = {} + for key, val in property_dict.items(): + np_array = np.array(val) + mean = '{:.2f}'.format(round(np_array.mean(), 2)) + standard_dev = '{:.2f}'.format(round(np_array.std(), 2)) + property_stats_dict[key] = str(mean) + " \u00B1 " + str(standard_dev) + return property_stats_dict + + +def generate_project_report(project_name: str, final_stats: dict[str, str], final_fixed_stats: dict[str, str]) -> dict[str, dict]: + final_report_dict = {project_name: final_stats, project_name + "-fixed": final_fixed_stats} + return final_report_dict + + +def generate_mph_project_df(final_stats: dict[str, str], final_fixed_stats: dict[str, str], final_naive_stats: dict[str, str], row_count: int) -> (pd.DataFrame(), int): + vanilla_df = pd.DataFrame() + vanilla_df['Property'] = [key for key in final_stats.keys()] + vanilla_df['Vanilla'] = [val for val in final_stats.values()] + + improved_df = pd.DataFrame() + improved_df['Property'] = [key for key in final_fixed_stats.keys()] + improved_df['Improved'] = [val for val in final_fixed_stats.values()] + + naive_df = pd.DataFrame() + + naive_df['Property'] = ['naive'] + naive_df['Vanilla'] = [final_stats['list']] + naive_df['Improved'] = [final_naive_stats['naive']] + + merged_df = pd.merge(vanilla_df, improved_df, how='outer', on='Property') + merged_final_df = pd.concat([merged_df, naive_df]).reset_index() + + merged_final_df['N'] = pd.RangeIndex(start=row_count, stop=len(merged_final_df.index) + row_count) + row_count += len(merged_final_df.index) + final_df = merged_final_df[['N', 'Property', 'Vanilla', 'Improved']] + return final_df, row_count + +def generate_project_df(final_stats: dict[str, str], final_fixed_stats: dict[str, str], row_count: int) -> (pd.DataFrame(), int): + vanilla_df = pd.DataFrame() + vanilla_df['Property'] = [key for key in final_stats.keys()] + vanilla_df['Vanilla'] = [val for val in final_stats.values()] + + improved_df = pd.DataFrame() + improved_df['Property'] = [key for key in final_fixed_stats.keys()] + improved_df['Improved'] = [val for val in final_fixed_stats.values()] + + merged_df = pd.merge(vanilla_df, improved_df, how='outer', on='Property') + merged_df['N'] = pd.RangeIndex(start=row_count, stop=len(merged_df.index) + row_count) + row_count += len(merged_df.index) + final_df = merged_df[['N', 'Property', 'Vanilla', 'Improved']] + return final_df, row_count + + +def main(): + final_dataset = {} + row_count = 1 + for project_name in PROJECTS: + fixed_project_name = project_name + "-fixed" + results_directory = BASE_RESULT_DIR + project_name + "/" + fixed_results_directory = BASE_RESULT_DIR + fixed_project_name + "/" + # vanilla + stats_directories = obtain_stats_directories(results_directory=results_directory) + evaluated_runs = filter_for_recent_results(project_name=project_name, stats_directories=stats_directories) + raw_stats = evaluate_directories(project_name=project_name, results_directory=results_directory, directories=evaluated_runs) + + # fixed + fixed_stats_directories = obtain_stats_directories(results_directory=fixed_results_directory) + evaluated_fixed_runs = filter_for_recent_results(project_name=project_name, stats_directories=fixed_stats_directories) + fixed_raw_stats = evaluate_directories(project_name=fixed_project_name, results_directory=fixed_results_directory, directories=evaluated_fixed_runs) + + if project_name == "mph-table": + naive_project_name = "mph-table-naive" + naive_results_directory = BASE_RESULT_DIR + naive_project_name + "/" + naive_stats_directories = obtain_stats_directories(results_directory=naive_results_directory) + evaluated_naive_runs = filter_for_recent_results(project_name=project_name, stats_directories=naive_stats_directories) + naive_stats = evaluate_directories(project_name=naive_project_name, results_directory=naive_results_directory, directories=evaluated_naive_runs) + # obtain mean/st dev + final_stats = generate_report_stats(stat_values=raw_stats) + final_fixed_stats = generate_report_stats(stat_values=fixed_raw_stats) + final_naive_stats = generate_report_stats(stat_values=naive_stats) + + project_df, row_count = generate_mph_project_df(final_stats=final_stats, final_fixed_stats=final_fixed_stats, final_naive_stats=final_naive_stats, row_count=row_count) + + final_dataset[project_name] = project_df + else: + # obtain mean/st dev + final_stats = generate_report_stats(stat_values=raw_stats) + final_fixed_stats = generate_report_stats(stat_values=fixed_raw_stats) + project_df, row_count = generate_project_df(final_stats=final_stats, final_fixed_stats=final_fixed_stats, row_count=row_count) + final_dataset[project_name] = project_df + + + with open(TEX_REPORT_NAME, 'w') as tf: + df = pd.DataFrame() + for project in PROJECTS: + final_dataset[project]['_style'] = '' + proj_mean_and_std = final_dataset[project][RAW_NAMES].copy() + vanilla_mean = pd.DataFrame(proj_mean_and_std['Vanilla'].apply(lambda v: float(v.split(" \u00B1 ")[0]) if + " \u00B1 " in str(v) else np.nan)).reset_index() + improved_mean = pd.DataFrame(proj_mean_and_std['Improved'].apply(lambda v: float(v.split(" \u00B1 ")[0]) if + " \u00B1 " in str(v) else np.nan)).reset_index() + + proj_stats = pd.merge(vanilla_mean, improved_mean, how='outer', on='index')[RAW_NAMES].reset_index() + + final_dataset[project]['Overhead'] = proj_stats[['Improved']].values / proj_stats[['Vanilla']].values + overhead_stats = final_dataset[project]['Overhead'].copy().reset_index() + + proj_mean = pd.merge(proj_stats, overhead_stats, how='outer', on='index')[CALC_NAMES].mean() + proj_mean['_style'] = 'BOLD' + proj_mean['N'] = '' + proj_mean['Property'] = 'Average' + final_dataset[project].loc['mean'] = proj_mean + + header = dict(zip(['N', 'Property', 'Vanilla', 'Improved', 'Overhead'], ['', '', '', '', ''])) + df = pd.concat([ + df, + pd.DataFrame(header | {'_style': 'HEADER', 'Property': project}, index=[0]), + final_dataset[project] + ], ignore_index=True) + # break + bold_rows = df[ df['_style'] == 'BOLD' ].index + header_rows = df[ df['_style'] == 'HEADER' ].index + + latexTable = df \ + .drop(columns=['_style']) \ + .style \ + .hide(axis=0) \ + .format(precision=2) \ + .set_properties(subset=pd.IndexSlice[header_rows, :], **{'HEADER': ''}) \ + .set_properties(subset=pd.IndexSlice[bold_rows, :], **{'textbf': '--rwrap'}) \ + .to_latex(hrules=False) + + outTable = '' + + # transform to sub headers + for line in latexTable.splitlines(keepends=True): + s = line.split('&') + c = str(len(s)) + + possibleCommand = s[0].strip() + + if possibleCommand == '\HEADER': + outTable += '\\hline' + "\n" + '\multicolumn{' + c + '}{c}{\\' + s[1].strip()[7:].strip().replace("-", "") + '}' + " \\\\\n" + '\\hline' + "\n" + else: + outTable += line.replace("nan", "-") + + tf.write(outTable) + + +if __name__ == "__main__": + main() diff --git a/artifacts/experiments/RQ4/generateStats.py b/artifacts/experiments/RQ4/generateStats.py new file mode 100755 index 00000000..434878a7 --- /dev/null +++ b/artifacts/experiments/RQ4/generateStats.py @@ -0,0 +1,37 @@ +import sys +import subprocess + +JAR_FILE = "target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar" + +def test_properties(project_name: str): + project_graph = project_name + "_graph" if project_name != "convex" else project_name + "-core_graph" + subprocess.run(["java", "-jar", JAR_FILE, "test", "-c", + project_name, "-f", project_graph]) + +def test_fixed_properties(project_name: str): + project_fixed_name = project_name + "-fixed" + project_graph = project_fixed_name + "_graph" if project_name != "convex" else project_name + "-core-fixed_graph" + subprocess.run(["java", "-jar", JAR_FILE, "test", "-c", + project_fixed_name, "-f", project_graph]) + +def test_naive_properties(project_name: str): + project_naive_name = project_name + "-naive" + project_graph = project_naive_name + "_graph" if project_name != "convex" else project_name + "-core-fixed_graph" + subprocess.run(["java", "-jar", JAR_FILE, "test", "-c", + project_naive_name, "-f", project_graph]) + + +def main(): + if not sys.argv[1]: + raise Exception("Must specify project name through command line param!") + project_name = sys.argv[1] + for index in range(10): + test_properties(project_name) + for index in range(10): + test_fixed_properties(project_name) + if project_name == "mph-table": + for index in range(10): + test_naive_properties(project_name) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/artifacts/output/README.md b/artifacts/output/README.md new file mode 100644 index 00000000..83a87e65 --- /dev/null +++ b/artifacts/output/README.md @@ -0,0 +1 @@ +## This is where output files generated from `run.py` (executing java-cg) will end up! diff --git a/artifacts/output/rq4.tex b/artifacts/output/rq4.tex new file mode 100644 index 00000000..9e52ccb0 --- /dev/null +++ b/artifacts/output/rq4.tex @@ -0,0 +1,37 @@ +\begin{tabular}{lllll} +N & Property & Vanilla & Improved & Overhead \\ +\hline +\multicolumn{5}{c}{\convex} \\ +\hline +1 & data & 38.90 ± 0.58 & nan & nan \\ +2 & message & 35.27 ± 0.62 & 30.69 ± 0.63 & 0.87 \\ +3 & primitive & 35.46 ± 0.52 & nan & nan \\ +\textbf{} & \textbf{Average} & \textbf{36.54} & \textbf{30.69} & \textbf{0.87} \\ +\hline +\multicolumn{5}{c}{\jflex} \\ +\hline +4 & addSingleton & 34.73 ± 0.42 & 41.92 ± 0.78 & 1.21 \\ +5 & contains & 34.34 ± 0.95 & 34.68 ± 0.86 & 1.01 \\ +6 & addSet & 34.49 ± 1.07 & 44.75 ± 0.94 & 1.30 \\ +7 & add & 34.60 ± 0.35 & 34.26 ± 0.45 & 0.99 \\ +8 & remove & 34.29 ± 0.68 & 34.10 ± 0.80 & 0.99 \\ +9 & addString & 34.04 ± 0.42 & 41.76 ± 1.04 & 1.23 \\ +10 & addSingle & 34.34 ± 1.04 & 44.63 ± 1.17 & 1.30 \\ +\textbf{} & \textbf{Average} & \textbf{34.40} & \textbf{39.44} & \textbf{1.15} \\ +\hline +\multicolumn{5}{c}{\mphtable} \\ +\hline +11 & list & 10.07 ± 0.23 & 20.72 ± 3.13 & 2.06 \\ +12 & naive & 10.07 ± 0.23 & 217.94 ± 15.96 & 21.64 \\ +\textbf{} & \textbf{Average} & \textbf{10.07} & \textbf{119.33} & \textbf{11.85} \\ +\hline +\multicolumn{5}{c}{\rpkicommons} \\ +\hline +13 & claiming & 24.51 ± 0.35 & nan & nan \\ +14 & aspa & 22.85 ± 0.33 & nan & nan \\ +15 & resources & 23.45 ± 0.36 & 25.77 ± 0.39 & 1.10 \\ +16 & roa & 22.52 ± 0.38 & nan & nan \\ +17 & manifest & 22.78 ± 0.38 & nan & nan \\ +18 & loose & 24.60 ± 0.45 & nan & nan \\ +\textbf{} & \textbf{Average} & \textbf{23.45} & \textbf{25.77} & \textbf{1.10} \\ +\end{tabular} diff --git a/artifacts/results/convex-fixed/.gitkeep b/artifacts/results/convex-fixed/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/artifacts/results/jflex-fixed/.gitkeep b/artifacts/results/jflex-fixed/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/artifacts/results/jflex/.gitkeep b/artifacts/results/jflex/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/artifacts/results/mph-table-fixed/.gitkeep b/artifacts/results/mph-table-fixed/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/artifacts/results/mph-table/.gitkeep b/artifacts/results/mph-table/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/artifacts/results/mph-table/TestSmartListSerializer#canRoundTripSerializableLists.html b/artifacts/results/mph-table/TestSmartListSerializer#canRoundTripSerializableLists.html new file mode 100644 index 00000000..8ca9b397 --- /dev/null +++ b/artifacts/results/mph-table/TestSmartListSerializer#canRoundTripSerializableLists.html @@ -0,0 +1 @@ +indeed-mph-table

indeed-mph-table

ElementMissed InstructionsCov.Missed BranchesCov.MissedCxtyMissedLinesMissedMethodsMissedClasses
Total9,534 of 9,7722%879 of 8901%8939221,7761,8414264535159
com.indeed.mph6,8610%5780%5925951,2381,2442862893233
com.indeed.mph.serializers2,6732177%301113%3013275385971401641926

Total Time Elapsed: 17.879733831 seconds

\ No newline at end of file diff --git a/artifacts/results/mph-table/TestSmartListSerializer#canRoundTripSerializableListsWithGenerator.html b/artifacts/results/mph-table/TestSmartListSerializer#canRoundTripSerializableListsWithGenerator.html new file mode 100644 index 00000000..fda6960b --- /dev/null +++ b/artifacts/results/mph-table/TestSmartListSerializer#canRoundTripSerializableListsWithGenerator.html @@ -0,0 +1 @@ +indeed-mph-table

indeed-mph-table

ElementMissed InstructionsCov.Missed BranchesCov.MissedCxtyMissedLinesMissedMethodsMissedClasses
Total9,405 of 9,7723%862 of 8903%8859221,7521,8414264535159
com.indeed.mph6,8610%5780%5925951,2381,2442862893233
com.indeed.mph.serializers2,54434611%284288%2933275145971401641926

Total Time Elapsed: 46.7510832 seconds

\ No newline at end of file diff --git a/artifacts/run.py b/artifacts/run.py index 1fc11e11..fd71517c 100644 --- a/artifacts/run.py +++ b/artifacts/run.py @@ -32,16 +32,6 @@ def require_program(program): panic("{} is not installed! Please install it and try again...".format(program), -1) -def prompt_and_strip_input(prompt): - """ - Strips whitespace from the user's input - :param prompt: the prompt to display to the user - :return: the users input without trailing or following whitespace - """ - - return input(prompt).strip() - - def extract_project_name(url): """ Extracts the name of the project from a git url @@ -60,20 +50,6 @@ def extract_project_name(url): return result.group(1) -def get_cloned_directory(url, cwd): - """ - Finds the directory which was created by `git clone` - :param url: the url of the repository that was cloned - :param cwd: the directory that the clone was executed in - :return: the directory which was created by cloning the url - """ - - path = cwd.joinpath(Path(extract_project_name(url))) - if path is None: - panic("Unable to find the directory that {} was cloned to".format(url), -1) - return path - - def verify_build_system_install(cfg): """ Determines which build system to use for the project @@ -145,12 +121,19 @@ def build_target_project(cfg, directory): def run_java_cg(root_dir, target_dir, cfg): - javacg_jar = "{}{}".format(root_dir, cfg["javacg-jar-location"]) - target_jar = "{}{}".format(target_dir, cfg["target-jar-location"]) + """ + Executes java-callgraph against the target project + :param root_dir: the directory that java-callgraph resides in + :param target_dir: the directory that the target project resides in + :param cfg: the run configuration + """ + + javacg_jar = Path("{}{}".format(root_dir, "/target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar")) + target_jar = Path("{}{}".format(target_dir, cfg["target-jar-location"])) cmd = "java -jar {} -j {}".format(javacg_jar, target_jar) if "coverage-location" in cfg: - cmd += " -c {}{}".format(target_dir, cfg["coverage-location"]) + cmd += " -c {}".format(Path("{}{}".format(target_dir, cfg["coverage-location"]))) if "entrypoint" in cfg: cmd += " -e {}".format(cfg["entrypoint"]) @@ -161,6 +144,9 @@ def run_java_cg(root_dir, target_dir, cfg): if "output-name" in cfg: cmd += " -o {}".format(cfg["output-name"]) + if "ancestry" in cfg: + cmd += " -a {}".format(cfg["ancestry"]) + os.chdir(Path("{}{}".format(root_dir, "/artifacts"))) print("Running `{}`".format(cmd)) subprocess.call([x for x in cmd.split()]) @@ -172,13 +158,14 @@ def run_java_cg(root_dir, target_dir, cfg): 2. Ask for the project's build system (e.g., maven) 3. Clone the project (e.g., `git clone ` 4. Build / Install the project (e.g., `mvn install`) + 5. Execute java-callgraph against the target project """ with open('config.yaml') as f: config = yaml.load(f, Loader=yaml.FullLoader) - javacg_directory = config["javacg-directory"] - if not Path(javacg_directory + "/target").exists(): + javacg_directory = Path(os.getcwd()).parent + if not Path("{}{}".format(javacg_directory, "/target")).exists(): build_java_callgraph(javacg_directory) # 1. Fetch the project's repository url @@ -193,4 +180,5 @@ def run_java_cg(root_dir, target_dir, cfg): # 4. Enter the project's directory & execute the build system build_target_project(config, target_directory) + # 5. Execute java-callgraph against the target project run_java_cg(javacg_directory, target_directory, config) diff --git a/assembly-dyn.xml b/assembly-dyn.xml index 5c342dcf..6316ccff 100644 --- a/assembly-dyn.xml +++ b/assembly-dyn.xml @@ -1,30 +1,30 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" + xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd"> - dycg-agent - - jar - - false + dycg-agent + + jar + + false - - - true - - javassist:javassist - - provided - - - - - - gr/gousiosg/javacg/dyn/*.class - - target/classes - / - - + + + true + + javassist:javassist + + provided + + + + + + gr/gousiosg/javacg/dyn/*.class + + target/classes + / + + diff --git a/assembly-st.xml b/assembly-st.xml index 80845349..ca6baa2e 100644 --- a/assembly-st.xml +++ b/assembly-st.xml @@ -1,30 +1,30 @@ + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2" + xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.2 http://maven.apache.org/xsd/assembly-1.1.2.xsd"> - static - - jar - - false + static + + jar + + false - - - true - - org.apache.bcel:bcel - - provided - - - - - - gr/gousiosg/javacg/stat/*.class - - target/classes - / - - + + + true + + org.apache.bcel:bcel + + provided + + + + + + gr/gousiosg/javacg/stat/*.class + + target/classes + / + + diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100644 index 00000000..4858965b --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +echo 'root access' +apt-get update +echo 'update' +apt-get install git -y +echo 'git install' +apt-get install openjdk-18-jre-headless -y +echo 'java install' +apt-get install maven -y +echo 'maven install' +apt-get install graphviz -y +echo 'graphviz install' +cd ~/Desktop +# git clone https://github.com/bitslab/java-callgraph +# /vagrant not mounting on my system by default currently. It is classified as an abnormal behavior and should work in general scenarios. +# added logic for package building after copying from vagrant file directory. +cp /vagrant ~/Desktop/java-callgraph +cd java-callgraph +mvn install \ No newline at end of file diff --git a/buildpng.sh b/buildpng.sh new file mode 100755 index 00000000..ada55472 --- /dev/null +++ b/buildpng.sh @@ -0,0 +1,14 @@ +cd output +mkdir -p "$1" +for i in `ls *-reachability.dot`; +do + echo Processing "$i"... + output=${i%.dot} + dot -Tpng -o "$output".png "$i" + echo Done +done +mv *.png "$1" +rm *.dot +rm *.ser +rm *.csv +echo Completed generating png files \ No newline at end of file diff --git a/buildsvg.sh b/buildsvg.sh new file mode 100755 index 00000000..8edeb22e --- /dev/null +++ b/buildsvg.sh @@ -0,0 +1,3 @@ +for i in `ls *-reachability.dot`; do echo -n Processing $i...; dot -Tsvg -o ${i::-4}.svg $i; echo Done; done; +for i in `ls *-annotated.dot`; do echo -n Processing $i...; dot -Tsvg -o ${i::-4}.svg $i; echo Done; done; + diff --git a/common.py b/common.py new file mode 100644 index 00000000..4b7d2d53 --- /dev/null +++ b/common.py @@ -0,0 +1,29 @@ +shortNames = { + "com.indeed.mph.serializers.TestSmartByteSerializer.canRoundTripBytes(B)V": 'byte', + "com.indeed.mph.serializers.TestSmartIntegerSerializer.canRoundTripIntegers(I)V": 'int', + "com.indeed.mph.serializers.TestSmartListSerializer.canRoundTripSerializableLists(Ljava/util/List;Ljava/util/List;Ljava/util/List;)V": 'list*', + "com.indeed.mph.serializers.TestSmartLongSerializer.canRoundTripLongs(J)V": 'long', + "com.indeed.mph.serializers.TestSmartOptionalSerializer.canRoundTripPresentOptionals(J)V": 'optionals', + "com.indeed.mph.serializers.TestSmartPairSerializer.canRoundTripPairs(Lcom/indeed/util/core/Pair;)V": 'pair', + "com.indeed.mph.serializers.TestSmartShortSerializer.canRoundTripShort(S)V": 'short', + "com.indeed.mph.serializers.TestSmartStringSerializer.canRoundTripStrings(Ljava/lang/String;)V": 'string', + "com.indeed.mph.serializers.TestSmartListSerializer.canRoundTripSerializableListsWithGenerator(Ljava/util/List;Ljava/util/List;Ljava/util/List;)V": 'list*', # new fixed + "convex.comms.GenTestFormat.dataRoundTrip(Lconvex/core/data/ACell;)V": 'data', + "convex.comms.GenTestFormat.messageRoundTrip(Ljava/lang/String;)V": 'message', + "convex.comms.GenTestFormat.primitiveRoundTrip(Lconvex/core/data/ACell;)V": 'primitive', + "jflex.core.unicode.CharClassesQuickcheck.addSet(Ljflex/core/unicode/CharClasses;Ljflex/core/unicode/IntCharSet;I)V": 'addSet', + "jflex.core.unicode.CharClassesQuickcheck.addSingle(Ljflex/core/unicode/CharClasses;II)V": 'addSingle', + "jflex.core.unicode.CharClassesQuickcheck.addSingleSingleton(Ljflex/core/unicode/CharClasses;I)V": 'addSingleton', + "jflex.core.unicode.CharClassesQuickcheck.addString(Ljflex/core/unicode/CharClasses;Ljava/lang/String;I)V": 'addString', + "jflex.state.StateSetQuickcheck.addStateDoesNotRemove(Ljflex/state/StateSet;I)V": 'add', + "jflex.state.StateSetQuickcheck.containsElements(Ljflex/state/StateSet;I)V": 'contains', + "jflex.state.StateSetQuickcheck.removeAdd(Ljflex/state/StateSet;I)V": 'remove*', + "jflex.state.StateSetQuickcheck.removeAddResize(Ljflex/state/StateSet;II)V": 'remove*', # new fixed + "net.ripe.rpki.commons.crypto.cms.roa.RoaCMSBuilderPropertyTest.buildEncodedParseCheck(JLjava/lang/Integer;)V": 'roa', + "net.ripe.rpki.commons.crypto.cms.manifest.ManifestCMSBuilderPropertyTest.buildEncodedParseCheck([BLjava/math/BigInteger;Ljava/lang/Integer;)V": 'manifest', + "net.ripe.rpki.commons.crypto.cms.aspa.AspaCmsTest.should_generate_aspa(ILjava/util/List;)V": 'aspa', + "net.ripe.rpki.commons.validation.X509ResourceCertificateParentChildValidatorTest.validParentChildSubResources(Ljava/util/List;I)V": 'resources*', + "net.ripe.rpki.commons.validation.X509ResourceCertificateParentChildValidatorTest.validParentChildOverClaiming(Ljava/util/List;ILjava/util/List;)V": 'claiming', + "net.ripe.rpki.commons.validation.X509ResourceCertificateParentChildValidatorTest.validParentChildOverClaimingLooseValidation(Ljava/util/List;ILjava/util/List;)V": 'loose', + "net.ripe.rpki.commons.validation.X509ResourceCertificateParentChildValidatorTest.validParentChildSubResources(Ljava/util/List;ILjava/util/List;)V": 'resources*', # new fixed +} diff --git a/configuredDiffImg.py b/configuredDiffImg.py new file mode 100755 index 00000000..7bc91045 --- /dev/null +++ b/configuredDiffImg.py @@ -0,0 +1,16 @@ +from diffimg import diff +from PIL import Image +import sys + +Image.MAX_IMAGE_PIXELS = None + +def main(): + expected = sys.argv[1] + actual = sys.argv[2] + print("Expected File: " + expected) + print("Actual File: " + actual) + difference = diff(expected, actual) + print("Difference in percent : " + str(difference) + "%") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/pom.xml b/pom.xml index 247e02f5..01da78c6 100644 --- a/pom.xml +++ b/pom.xml @@ -1,160 +1,204 @@ - - 4.0.0 + + 4.0.0 - gr.gousiosg - javacg - 0.1-SNAPSHOT - jar + gr.gousiosg + javacg + 0.1-SNAPSHOT + jar - javacg + javacg - - UTF-8 - + + UTF-8 + - - - org.reflections - reflections - 0.9.12 - - - commons-cli - commons-cli - 1.4 - - - org.slf4j - slf4j-api - 1.7.30 - - - org.slf4j - slf4j-simple - 1.7.30 - - - org.jgrapht - jgrapht-core - 1.5.0 - - - org.jgrapht - jgrapht-io - 1.5.0 - - - org.apache.bcel - bcel - 6.2 - - - javassist - javassist - 3.12.1.GA - - - io.cucumber - cucumber-java - 2.3.1 - test - - - io.cucumber - cucumber-junit - 2.3.1 - test - - - junit - junit - 4.12 - test - - - javax.xml.parsers - jaxp-api - 1.4.5 - + + + org.reflections + reflections + 0.9.12 + + + commons-cli + commons-cli + 1.4 + + + org.slf4j + slf4j-api + 1.7.30 + + + org.slf4j + slf4j-simple + 1.7.30 + + + org.jgrapht + jgrapht-core + 1.5.0 + + + org.jgrapht + jgrapht-io + 1.5.0 + + + org.apache.bcel + bcel + 6.2 + + + javassist + javassist + 3.12.1.GA + + + io.cucumber + cucumber-java + 2.3.1 + test + + + io.cucumber + cucumber-junit + 2.3.1 + test + + + junit + junit + 4.12 + test + + + javax.xml.parsers + jaxp-api + 1.4.5 + + + org.yaml + snakeyaml + 1.21 + + + org.eclipse.jgit + org.eclipse.jgit + 5.9.0.202009080501-r + - - org.ow2.asm - asm - 7.0 - + + org.ow2.asm + asm + 7.0 + - - - org.codehaus.mojo - jaxb2-maven-plugin - 2.5.0 - + + + org.codehaus.mojo + jaxb2-maven-plugin + 2.5.0 + - + + org.sonatype.sisu + sisu-guava + 0.9.9 + provided + + + com.opencsv + opencsv + 5.2 + + - - - - org.codehaus.mojo - jaxb2-maven-plugin - 2.5.0 - - - xjc - - xjc - - - - - - src/main/resources/jacoco-schema.xsd - - gr.gousiosg.javacg.stat.support.coverage - - + + + + org.apache.maven.plugins + maven-failsafe-plugin + 2.22.0 + + false + + **/**/*IT.java + **/*IT.java + **IT.java + + alphabetical + + + + + integration-test + verify + + + + - - org.apache.maven.plugins - maven-assembly-plugin - 3.0.0 - - - assembly-dyn.xml - assembly-st.xml - - - - true - gr.gousiosg.javacg.dyn.Instrumenter - gr.gousiosg.javacg.stat.JCallGraph - - - - jar-with-dependencies - - - - - make-assembly - package - - single - - - - + + org.codehaus.mojo + jaxb2-maven-plugin + 2.5.0 + + + xjc + + xjc + + + + + + src/main/resources/jacoco-schema.xsd + + gr.gousiosg.javacg.stat.coverage + + - - org.apache.maven.plugins - maven-compiler-plugin - - 11 - 11 - - + + org.apache.maven.plugins + maven-assembly-plugin + 3.0.0 + + + assembly-dyn.xml + assembly-st.xml + + + + true + gr.gousiosg.javacg.dyn.Instrumenter + gr.gousiosg.javacg.stat.JCallGraph + + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + - - + + org.apache.maven.plugins + maven-compiler-plugin + + 11 + 11 + + + + + diff --git a/quickcheck.md b/quickcheck.md index a8b0f96b..f057c065 100644 --- a/quickcheck.md +++ b/quickcheck.md @@ -1,8 +1,11 @@ ## Projects using [JUnit-QuickCheck](https://github.com/pholser/junit-quickcheck) -You can find many artifacts using the dependency here: [https://mvnrepository.com/artifact/com.pholser/junit-quickcheck-core/usages](https://mvnrepository.com/artifact/com.pholser/junit-quickcheck-core/usages). +You can find many artifacts using the dependency +here: [https://mvnrepository.com/artifact/com.pholser/junit-quickcheck-core/usages](https://mvnrepository.com/artifact/com.pholser/junit-quickcheck-core/usages) +. -Alternatively, you can search GitHub for Maven POM files containing JUnit-Quickcheck: https://github.com/search?l=Maven+POM&p=1&q=junit-quickcheck-core&type=Code +Alternatively, you can search GitHub for Maven POM files containing +JUnit-Quickcheck: https://github.com/search?l=Maven+POM&p=1&q=junit-quickcheck-core&type=Code For example, here are some that I've found: diff --git a/rq1_coverage_improvement.py b/rq1_coverage_improvement.py new file mode 100644 index 00000000..dab4a4cc --- /dev/null +++ b/rq1_coverage_improvement.py @@ -0,0 +1,131 @@ +import pandas as pd +from common import shortNames + +# IMPROVEMENT IN COVERAGE + +FIELD_N = 'N' +FIELD_PROPERTY = 'Property' +FIELD_IMPROVED_LOC_COVERAGE = 'Fixed' # 'Improved LOC Coverage' +FIELD_ORIGINAL_LOC_COVERAGE = 'Vanilla' # 'Original LOC Coverage' +FIELD_LOC_COUNT_IMPROVEMENT = 'Improved' # 'Improvement' + +PROP_NAMES = [FIELD_N, FIELD_PROPERTY] +CALC_NAMES = [FIELD_IMPROVED_LOC_COVERAGE, FIELD_ORIGINAL_LOC_COVERAGE, FIELD_LOC_COUNT_IMPROVEMENT] +TABLE_HEADER = PROP_NAMES + CALC_NAMES + +projects = [ + ('convex', 'artifacts/experiment/rq1_convex.csv', 'artifacts/experiment/rq1_convex-fixed.csv'), + ('jflex', 'artifacts/experiment/rq1_jflex.csv', 'artifacts/experiment/rq1_jflex-fixed.csv'), + ('mphtable', 'artifacts/experiment/rq1_mph-table.csv', 'artifacts/experiment/rq1_mph-table-fixed.csv'), + ('rpkicommons', 'artifacts/experiment/rq1_rpki-commons.csv', 'artifacts/experiment/rq1_rpki-commons-fixed.csv'), +] + +allCoverageFile = 'artifacts/experiment/rq1_table_coverage.tex' + +dataSet = pd.DataFrame() +dataSetSum = {} +rowCount = 1 + +for project in projects: + projName = project[0] + csvFile = project[1] + fixedCsvFile = project[2] + + original = pd.read_csv(csvFile, sep=',', header=0) + original = original[ original['inPrunedGraph'] == "Y"] # only include actual reachable methods + original['entryPointKey'] = original['entryPoint'].apply(lambda v: v.split("(", 1)[0]) + + fixed = pd.read_csv(fixedCsvFile, sep=',', header=0) + fixed = fixed[ fixed['inPrunedGraph'] == "Y"] # only include actual reachable methods + fixed['entryPointKey'] = fixed['entryPoint'].apply(lambda v: v.split("(", 1)[0]) + fixed.rename(columns=lambda x: x if x == 'entryPointKey' or x == 'method' else 'FIXED_'+x, inplace=True) + + data = pd.merge(fixed, original, on=['entryPointKey', 'method'], how='left') + data['entryPoint'].fillna(data['FIXED_entryPoint'], inplace=True) + + # drop rows where we don't have "FIXED" + #data = data[ data['FIXED_linesCovered'] != "UNK" ] + + data['Project'] = projName + data[FIELD_ORIGINAL_LOC_COVERAGE] = data['linesCovered'].apply(lambda v: 0 if v == "UNK" else v).astype(float) + data[FIELD_IMPROVED_LOC_COVERAGE] = data['FIXED_linesCovered'].apply(lambda v: 0 if v == "UNK" else v).astype(float) + data[FIELD_LOC_COUNT_IMPROVEMENT] = 0 + + # add Name as a friendly name for each entrypoint + data[FIELD_PROPERTY] = data['entryPoint'].apply(lambda v: shortNames[v]) + + df = data[[FIELD_PROPERTY]+CALC_NAMES].groupby(by=FIELD_PROPERTY).sum().round(2) + df[FIELD_N] = pd.RangeIndex(start=rowCount, stop=len(df.index) + rowCount) + df.reset_index(inplace=True) + dfSubset = df[PROP_NAMES + CALC_NAMES] + + rowCount = len(df.index) + rowCount + dataSetSum[projName] = dfSubset.copy() + dataSetSum[projName][FIELD_LOC_COUNT_IMPROVEMENT] = dataSetSum[projName][FIELD_IMPROVED_LOC_COVERAGE] - \ + dataSetSum[projName][FIELD_ORIGINAL_LOC_COVERAGE] + + # show only records that have improvement + dataSetSum[projName] = dataSetSum[projName][ dataSetSum[projName][FIELD_LOC_COUNT_IMPROVEMENT] > 0 ] + + #dataSetSum[projName][FIELD_LOC_PERCENT_IMPROVEMENT] = \ + # dataSetSum[projName][FIELD_IMPROVED_LOC_COVERAGE] / dataSetSum[projName][FIELD_ORIGINAL_LOC_COVERAGE] + + #dataSetSum[projName][FIELD_METHOD_PERCENT_IMPROVEMENT] = \ + # dataSetSum[projName][FIELD_IMPROVED_METHOD_COVERAGE] / dataSetSum[projName][FIELD_ORIGINAL_METHOD_COVERAGE] + + dataSet = pd.concat([dataSet, data.copy()]) + +# output all projects with project headings +with open(allCoverageFile, 'w') as tf: + newDF = pd.DataFrame() + + for project in projects: + projName = project[0] + dataSetSum[projName]['_style'] = '' + + projMean = dataSetSum[projName][CALC_NAMES].mean() + projMean['_style'] = 'BOLD' + projMean[FIELD_N] = '' + projMean[FIELD_PROPERTY] = 'Average' + dataSetSum[projName].loc['mean'] = projMean + + header = dict(zip(TABLE_HEADER, map(lambda v: '', TABLE_HEADER))) + + newDF = pd.concat([ + newDF, + pd.DataFrame(header | {'_style': 'HEADER', FIELD_PROPERTY: projName}, index=[0]), # project header + dataSetSum[projName] # project data / avg + ], ignore_index=True) + + bold_rows = newDF[ newDF['_style'] == 'BOLD' ].index + header_rows = newDF[ newDF['_style'] == 'HEADER' ].index + data_rows = newDF[ newDF['_style'] != 'HEADER' ].index + + latexTable = newDF \ + .drop(columns=['_style']) \ + .style \ + .hide(axis=0) \ + .format({ + FIELD_IMPROVED_LOC_COVERAGE: "{:.0f}", + FIELD_ORIGINAL_LOC_COVERAGE: "{:.0f}", + FIELD_LOC_COUNT_IMPROVEMENT: "+{:.0f}", + }, subset=pd.IndexSlice[data_rows, :]) \ + .set_properties(subset=pd.IndexSlice[header_rows, :], **{'HEADER': ''}) \ + .set_properties(subset=pd.IndexSlice[bold_rows, :], **{'textbf': '--rwrap'}) \ + .to_latex(hrules=False, column_format="llrrrrrr") + + outTable = '' + + # transform to sub headers + for line in latexTable.splitlines(keepends=True): + s = line.split('&') + c = str(len(s)) + + possibleCommand = s[0].strip() + + if possibleCommand == '\HEADER': + outTable += '\\hline' + "\n" + '\multicolumn{' + c + '}{c}{\\' + s[1].strip()[7:].strip() + '}' + " \\\\\n" + '\\hline' + "\n" + else: + outTable += line + + tf.write(outTable) \ No newline at end of file diff --git a/rq1_jacoco_vs_sysname.py b/rq1_jacoco_vs_sysname.py new file mode 100644 index 00000000..363aa0da --- /dev/null +++ b/rq1_jacoco_vs_sysname.py @@ -0,0 +1,167 @@ +import pandas as pd +from common import shortNames + +FIELD_N = 'N' +FIELD_PROPERTY = 'Property' +FIELD_JACOCO = '\\jacoco' +FIELD_SYSNAME = '\\sysname' +FIELD_REACHABLE = 'Reachable' +FIELD_IMPOSSIBLE = 'Impossible' +FIELD_MISSED = 'Missed' +FIELD_FIRST = 'First' +FIELD_SECOND = 'Second' +FIELD_THIRD = 'Third' + +PROP_NAMES = [FIELD_N, FIELD_PROPERTY] +CALC_NAMES = [FIELD_JACOCO, FIELD_IMPOSSIBLE, FIELD_MISSED, FIELD_SYSNAME, FIELD_FIRST, FIELD_SECOND, FIELD_THIRD] +TABLE_HEADER = PROP_NAMES + CALC_NAMES + +projects = [ + ('convex', 'artifacts/experiment/rq1_convex.csv', 'artifacts/experiment/rq1_paths_convex.csv', 'artifacts/experiment/rq1_table_convex.tex'), + ('jflex', 'artifacts/experiment/rq1_jflex.csv', 'artifacts/experiment/rq1_paths_jflex.csv', 'artifacts/experiment/rq1_table_jflex.tex'), + ('mphtable', 'artifacts/experiment/rq1_mph-table.csv', 'artifacts/experiment/rq1_paths_mph-table.csv', 'artifacts/experiment/rq1_table_mph-table.tex'), + ('rpkicommons', 'artifacts/experiment/rq1_rpki-commons.csv', 'artifacts/experiment/rq1_paths_rpki-commons.csv', 'artifacts/experiment/rq1_table_rpki-commons.tex'), +] + +byProjNameFile = 'artifacts/experiment/rq1_table_projects.tex' + +byAllEntrypointNameFile = 'artifacts/experiment/rq1_table_all_entrypoints.tex' + +dataSet = pd.DataFrame() +dataSetSum = {} +rowCount = 1 + +for project in projects: + projName = project[0] + csvFile = project[1] + csvPaths = project[2] + texFile = project[3] + + dataPaths = pd.read_csv(csvPaths, sep=',', header=0) + dataPaths['Project'] = projName + + data = pd.read_csv(csvFile, sep=',', header=0) + data['Project'] = projName + data['inJaCoCo'] = data['inJaCoCo'] == "Y" #convert Y/N to True/False + data['inPrunedGraph'] = data['inPrunedGraph'] == "Y" #convert Y/N to True/False + + data['reachableJaCoCo'] = data['inJaCoCo'] + data['reachableProperty'] = data['inPrunedGraph'] + + + # false-positives: tool identifies code as reachable, + # but cannot be reached by a property test + data['FP'] = (data['reachableJaCoCo'] & ~data['reachableProperty']) + data['FP'] = data['FP'].apply(lambda v: 1 if v else 0) + + # false-negatives: code that is reachable from the property + # test but the tool does not identify it as such + data['FN'] = (~data['reachableJaCoCo'] & data['reachableProperty']) + data['FN'] = data['FN'].apply(lambda v: 1 if v else 0) + + # JaCoCo and our tool agree that is reachability + data['TP'] = (data['reachableJaCoCo'] & data['reachableProperty']) + data['TP'] = data['TP'].apply(lambda v: 1 if v else 0) + + # JaCoCo and our tool agree that is NOT reachable + data['TN'] = (~data['reachableJaCoCo'] & ~data['reachableProperty']) + data['TN'] = data['TN'].apply(lambda v: 1 if v else 0) + + # add Name as a friendly name for each entrypoint + data[FIELD_PROPERTY] = data['entryPoint'].apply(lambda v: shortNames[v]) + dataPaths[FIELD_PROPERTY] = dataPaths['entryPoint'].apply(lambda v: shortNames[v]) + + dfGrouped = data[[FIELD_PROPERTY, 'FP', 'FN', 'TP']].groupby(by=FIELD_PROPERTY).sum().round(2) + df = dfGrouped.merge(dataPaths[[FIELD_PROPERTY, 'First', 'Second', 'Third']], on=FIELD_PROPERTY, how='left') + + # pd.concat([dfGrouped, dataPaths], axis=1, keys=['entryPoint'], join="left") + df[FIELD_JACOCO] = df['FP'] + df['TP'] + df[FIELD_REACHABLE] = df['TP'] + df[FIELD_IMPOSSIBLE] = df['FP'] + df[FIELD_MISSED] = df['FN'] + df[FIELD_SYSNAME] = df['FN'] + df['TP'] + df[FIELD_N] = pd.RangeIndex(start=rowCount, stop=len(df.index) + rowCount) + df.reset_index(inplace=True) + dfSubset = df[TABLE_HEADER] + + rowCount = len(df.index) + rowCount + dataSetSum[projName] = dfSubset.copy() + + with open(texFile, 'w') as tf: + tf.write(dfSubset.style.hide(axis="index").to_latex()) + + dataSet = pd.concat([dataSet, data.copy()]) + + +# output sum group by projName +with open(byProjNameFile, 'w') as tf: + fpfnSum = dataSet[['Project', 'FP', 'FN', 'TP']]\ + .sort_values(by='Project')\ + .groupby(by='Project')\ + .sum() + + fpfnSum['Total'] = dataSet[['Project']].groupby(by='Project').size() + tf.write(fpfnSum.reset_index().style.hide(axis="index").to_latex()) + + +# output all projects with project headings +with open(byAllEntrypointNameFile, 'w') as tf: + newDF = pd.DataFrame() + + for project in projects: + projName = project[0] + dataSetSum[projName]['_style'] = '' + + projMean = dataSetSum[projName][CALC_NAMES].mean().round() + projMean['_style'] = 'BOLD' + projMean[FIELD_N] = '' + projMean[FIELD_PROPERTY] = 'Average' + dataSetSum[projName].loc['mean'] = projMean + + header = dict(zip(TABLE_HEADER, map(lambda v: '', TABLE_HEADER))) + + newDF = pd.concat([ + newDF, + pd.DataFrame(header | {'_style': 'HEADER', FIELD_PROPERTY: projName}, index=[0]), # project header + dataSetSum[projName] # project data / avg + ], ignore_index=True) + + bold_rows = newDF[ newDF['_style'] == 'BOLD' ].index + header_rows = newDF[ newDF['_style'] == 'HEADER' ].index + data_rows = newDF[ newDF['_style'] != 'HEADER' ].index + + impossiblePercent = newDF[FIELD_IMPOSSIBLE].apply(lambda x: "0" if x == "" else x).astype('int') / newDF[FIELD_JACOCO].apply(lambda x: "0" if x=="" else x).astype('int') + newDF[FIELD_IMPOSSIBLE] = list(zip(newDF[FIELD_IMPOSSIBLE], impossiblePercent * 100)) + + latexTable = newDF \ + .drop(columns=['_style']) \ + .style \ + .hide(axis=0) \ + .format({ + FIELD_JACOCO: "{:.0f}", + FIELD_IMPOSSIBLE: lambda x: "-{:.0f} ({:.0f}\%)".format(*x), + FIELD_MISSED: "+{:.0f}", + FIELD_SYSNAME: "{:.0f}", + FIELD_FIRST: "{:.0f}", + FIELD_SECOND: "{:.0f}", + FIELD_THIRD: "{:.0f}" + }, subset=pd.IndexSlice[data_rows, :], na_rep="-") \ + .set_properties(subset=pd.IndexSlice[header_rows, :], **{'HEADER': ''}) \ + .set_properties(subset=pd.IndexSlice[bold_rows, :], **{'textbf': '--rwrap'}) \ + .to_latex(hrules=False, column_format="llrrrrrrr") + + outTable = '' + + # transform to sub headers + for line in latexTable.splitlines(keepends=True): + s = line.split('&') + c = str(len(s)) + + possibleCommand = s[0].strip() + + if possibleCommand == '\HEADER': + outTable += '\\hline' + "\n" + '\multicolumn{' + c + '}{c}{\\' + s[1].strip()[7:].strip() + '}' + " \\\\\n" + '\\hline' + "\n" + else: + outTable += line + + tf.write(outTable) \ No newline at end of file diff --git a/run_experiment_rq1.sh b/run_experiment_rq1.sh new file mode 100755 index 00000000..43b8bf40 --- /dev/null +++ b/run_experiment_rq1.sh @@ -0,0 +1,31 @@ +#!/bin/bash + +# exit if anything throws a bad exit code +set -e + +# SET JCG_HOME based on the directory where this script resides +JCG_HOME="$(pwd)/$( dirname -- "$0"; )"; + +cd $JCG_HOME || exit + +mkdir -p artifacts/experiment + +for PROJECT in mph-table convex jflex rpki-commons mph-table-fixed convex-fixed jflex-fixed rpki-commons-fixed +do + FILE=artifacts/experiment/rq1_$PROJECT.csv + COUNTFILE=artifacts/experiment/rq1_paths_$PROJECT.csv + + # run experiment + echo Running RQ1 for project $PROJECT with output going to $FILE + java -cp target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar edu.uic.bitslab.callgraph.Comparison -p $PROJECT -o $FILE -c $COUNTFILE + + # check that the expected file exists + if [ ! -f $FILE ]; then + echo "Experiment did not produce the expected output file" + exit + fi +done + +python rq1_jacoco_vs_sysname.py + + diff --git a/run_experiment_rq4.sh b/run_experiment_rq4.sh new file mode 100755 index 00000000..9ab3bd7e --- /dev/null +++ b/run_experiment_rq4.sh @@ -0,0 +1,9 @@ +GEN_STATS="artifacts/experiments/RQ4/generateStats.py" +GEN_RESULTS="artifacts/experiments/RQ4/generateResults.py" +for PROJECT in convex jflex mph-table rpki-commons +do + echo Generating statistics for $PROJECT + python3 $GEN_STATS $PROJECT +done +echo Generating results for projects +python3 $GEN_RESULTS \ No newline at end of file diff --git a/runall.sh b/runall.sh new file mode 100755 index 00000000..0fdd2d35 --- /dev/null +++ b/runall.sh @@ -0,0 +1,27 @@ +#!/bin/bash + +# SET JCG_HOME based on the directory where this script resides +JCG_HOME="$(pwd)/$( dirname -- "$0"; )"; + +cd $JCG_HOME || exit + +mkdir -p serializedGraphs + + +for type in original fixed +do + for project in convex jflex mph-table rpki-commons + do + echo $type for $project + + if [[ "$type" == "original" ]] + then + projectName=$project + else + projectName=$project-$type + fi + + ./runone.sh $projectName + done +done + diff --git a/runone.sh b/runone.sh new file mode 100755 index 00000000..b936b1ef --- /dev/null +++ b/runone.sh @@ -0,0 +1,45 @@ +#!/bin/bash + +# exit if anything throws a bad exit code +set -e + +# SET JCG_HOME based on the directory where this script resides +JCG_HOME="$(pwd)/$( dirname -- "$0"; )"; + +cd $JCG_HOME || exit + +mkdir -p serializedGraphs + +# check $1 to be sure it is at least 1 character and only contains alpha, number, _, and -. +if [[ $# -ne 1 || ! $1 =~ ^[A-Za-z0-9_\-]+$ ]]; then + echo "Provide a project name (alphanumeric with _ and - allowed)."; +else + projectName=$1 + + # clean project + rm -rf "$projectName" + + # clean output + rm -rf output + mkdir output + + # add results (if not exists) + mkdir -p "artifacts/results/$projectName" + + # git project + java -jar ./target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar git -c $projectName + + # build project + java -jar ./target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar build -c $projectName -o serializedGraphs/$projectName + + # test project + java -jar ./target/javacg-0.1-SNAPSHOT-jar-with-dependencies.jar test -c $projectName -f serializedGraphs/$projectName + + # copy output + rm -rf output-$projectName + mv output output-$projectName + + cd output-$projectName || exit + ../buildsvg.sh + cd .. +fi diff --git a/src/main/java/edu/uic/bitslab/callgraph/Comparison.java b/src/main/java/edu/uic/bitslab/callgraph/Comparison.java new file mode 100644 index 00000000..1d682ec9 --- /dev/null +++ b/src/main/java/edu/uic/bitslab/callgraph/Comparison.java @@ -0,0 +1,440 @@ +package edu.uic.bitslab.callgraph; + +import com.opencsv.CSVReader; +import gr.gousiosg.javacg.dyn.Pair; +import gr.gousiosg.javacg.stat.coverage.ColoredNode; +import gr.gousiosg.javacg.stat.coverage.JacocoCoverage; +import gr.gousiosg.javacg.stat.coverage.Report; +import gr.gousiosg.javacg.stat.support.RepoTool; +import org.apache.commons.cli.*; +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultEdge; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.*; +import java.util.stream.Collectors; + +public class Comparison { + private static final String PROJECT_NAME = "p"; + private static final String PROJECT_NAME_LONG = "project"; + private static final String OUTPUT_FILE_NAME = "o"; + private static final String OUTPUT_FILE_NAME_LONG = "output"; + private static final String ARTIFACT_TIMESTAMP = "t"; + private static final String ARTIFACT_TIMESTAMP_LONG = "timestamp"; + private static final String OUTPUT_PATH_FILE_NAME = "c"; + private static final String OUTPUT_PATH_FILE_NAME_LONG = "outpathcount"; + + + private static final Logger LOGGER = LoggerFactory.getLogger(Comparison.class); + + private static String getKey(String entryPoint, String method) { + return entryPoint + ":" + method; + } + + + public Map pruneAndJaCoCo(String jacocoXMLFilename, String prunedGraphSerFile) { + Map rpt = new HashMap<>(); + + try { + Graph prunedGraph; + + try (ObjectInput ois = new ObjectInputStream(new FileInputStream(prunedGraphSerFile))) { + prunedGraph = returnGraph(ois.readObject()); + } + + if (prunedGraph == null) { + throw new Exception("pruned graph is null"); + } + + String entryPoint = prunedGraph + .vertexSet() + .stream() + .filter(v -> prunedGraph.inDegreeOf(v) == 0) + .map(ColoredNode::getLabel) + .findFirst() + .orElse(null); + + for (ColoredNode v : prunedGraph.vertexSet()) { + String method = v.getLabel(); + String key = getKey(entryPoint, method); + + Row r = rpt.getOrDefault(key, new Row(entryPoint, method)); + r.addPruned(v.getColor()); + rpt.put(key, r); + } + + JacocoCoverage jacocoCoverage = new JacocoCoverage(jacocoXMLFilename); + Map methodCoverage = jacocoCoverage.getMethodCoverage(); + methodCoverage.forEach( + (method, details) -> { + String key = getKey(entryPoint, method); + Row r = rpt.getOrDefault(key, new Row(entryPoint, method)); + + + int instructionCovered = Integer.MIN_VALUE; + int instructionMissed = Integer.MIN_VALUE; + int branchesCovered = Integer.MIN_VALUE; + int branchedMissed = Integer.MIN_VALUE; + int linesCovered = Integer.MIN_VALUE; + int linesMissed = Integer.MIN_VALUE; + int complexityCovered = Integer.MIN_VALUE; + int complexityMissed = Integer.MIN_VALUE; + int methodCovered = Integer.MIN_VALUE; + int methodMissed = Integer.MIN_VALUE; + + + for (Report.Package.Class.Method.Counter counter : details.getCounter()) { + switch (counter.getType()) { + case "INSTRUCTION": + instructionCovered = counter.getCovered(); + instructionMissed = counter.getMissed(); + break; + case "BRANCH": + branchesCovered = counter.getCovered(); + branchedMissed = counter.getMissed(); + break; + case "LINE": + linesCovered = counter.getCovered(); + linesMissed = counter.getMissed(); + break; + case "COMPLEXITY": + complexityCovered = counter.getCovered(); + complexityMissed = counter.getMissed(); + break; + case "METHOD": + methodCovered = counter.getCovered(); + methodMissed = counter.getMissed(); + break; + } + } + + r.addJaCoCo(instructionCovered, instructionMissed, branchesCovered, branchedMissed, linesCovered, linesMissed, complexityCovered, complexityMissed, methodCovered, methodMissed); + + rpt.put(key, r); + } + ); + + return rpt; + } catch (Exception exception) { + LOGGER.error(exception.getMessage(), exception); + } + + return null; + } + + public Map> pathCounts(List pathFiles) throws Exception { + Map> paths = new HashMap<>(); + + for (String pathFile : pathFiles) { + if (!Files.exists(Path.of(pathFile))) { + continue; + } + + List pathsList = new ArrayList<>(); + + try (CSVReader csvReader = new CSVReader(new FileReader(pathFile))) { + String[] values; + String entryPoint = null; + + while ((values = csvReader.readNext()) != null) { + // skip lines with less that expected values (expect 3+ columns) + if (values.length < 3) continue; + + String nextEntryPoint = values[2]; + + // skip empty entrypoints + if (nextEntryPoint.isEmpty()) continue; + + // we don't have an entryPoint so set it + if (entryPoint == null) entryPoint = nextEntryPoint; + + // put in some guard rails + if (!entryPoint.equals(nextEntryPoint)) { + throw new Exception("Entrypoint should be same within each file"); + } + + int pathLength = values.length - 2; + + pathsList.add(pathLength); + } + + if (entryPoint != null) { + paths.put( + entryPoint, + pathsList.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList()) + ); + } + } + } + + return paths; + } + + @SuppressWarnings("unchecked") + private Graph returnGraph(Object o) { + if (o instanceof Graph) { + return (Graph) o; + } + + LOGGER.error("Expected instanceof Graph, but received " + o.getClass().getName() + " instead."); + return null; + } + + private static Path getLatestResultPath(String project) throws IOException { + RepoTool rt = new RepoTool(project); + String resultsDir = "artifacts/results/" + project; + String glob = (rt.getSubProject().equals("") ? project : rt.getSubProject()) + "????-??-??T??_??_??.??????"; + Path latestPath = null; + + for (Path path : Files.newDirectoryStream(Path.of(resultsDir), glob)) { + if (latestPath == null || path.compareTo(latestPath) > 0) { + latestPath = path; + } + } + + return latestPath; + } + + + + public static void main(String[] args) throws Exception { + String project = null; + String timeStamp = null; + String outputFile = null; + String outputPathFile = null; + + List filePaths = new ArrayList<>(); + + /* Setup cmdline argument parsing */ + CommandLineParser parser = new DefaultParser(); + Options options = getOptions(); + CommandLine cmd; + + try { + cmd = parser.parse(options, args); + if (cmd.hasOption(PROJECT_NAME)) { + project = cmd.getOptionValue(PROJECT_NAME); + } + + if (cmd.hasOption(ARTIFACT_TIMESTAMP)) { + timeStamp = cmd.getOptionValue(ARTIFACT_TIMESTAMP); + } + + if (cmd.hasOption(OUTPUT_FILE_NAME)) { + outputFile = cmd.getOptionValue(OUTPUT_FILE_NAME); + } + + if (cmd.hasOption(OUTPUT_PATH_FILE_NAME)) { + outputPathFile = cmd.getOptionValue(OUTPUT_PATH_FILE_NAME); + } + } catch(ParseException pe) { + LOGGER.error("Error parsing command-line arguments: " + pe.getMessage()); + LOGGER.error("Please, follow the instructions below:"); + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("Build comparison for RQ1", options); + System.exit(1); + } + + if (timeStamp == null) { + // get last one in results + + Path latestPath = getLatestResultPath(project); + + if (latestPath == null) { + LOGGER.error("No result directory found for " + project + "."); + System.exit(1); + } + + assert project != null; + + String dirPath = latestPath.getFileName().toString(); + timeStamp = dirPath.substring(dirPath.length() - 26); + } + + Comparison comparison = new Comparison(); + + RepoTool rt = new RepoTool(project, timeStamp); + List> coverageFiles = rt.obtainCoverageFilesAndEntryPoints(); + Map rpt = new HashMap<>(); + + for (Pair coverageFile : coverageFiles) { + // need pruned graph ser file part of artifacts! + String jacocoXMLFilename = coverageFile.first; + String prunedGraphSerFile = coverageFile.first.substring(0, coverageFile.first.length()-4) + "-reachability.ser"; + filePaths.add(coverageFile.first.substring(0, coverageFile.first.length()-4) + "-paths.csv"); + + rpt.putAll(comparison.pruneAndJaCoCo(jacocoXMLFilename, prunedGraphSerFile)); + } + + String header = String.join(",", + "entryPoint", "method", "nodeColor", + "instructionCovered", "instructionMissed", + "branchesCovered", "branchesMissed", + "linesCovered", "linesMissed", + "complexityCovered","complexityMissed", + "methodCovered","methodMissed", + "inJaCoCo", "inPrunedGraph" + ); + + if (outputFile == null) { + System.out.println(header); + rpt.forEach((k, r) -> System.out.println(r)); + } else { + try(FileWriter writer = new FileWriter(outputFile)) { + writer.write(header + "\n"); + + for (Row r : rpt.values()) { + writer.write(r + "\n"); + } + } + } + + // build path counts + String headerPath = String.join(",", "entryPoint", "First", "Second", "Third"); + + Map> paths = comparison.pathCounts(filePaths); + + if (outputPathFile == null) { + System.out.println(headerPath); + paths.forEach( + (entryPoint, pathCounts) -> System.out.println( + "\"" + entryPoint + "\"," + + pathCounts.stream().limit(3).map(Object::toString).collect(Collectors.joining(",")) + ) + ); + } else { + try(FileWriter writer = new FileWriter(outputPathFile)) { + writer.write(headerPath + "\n"); + + for (Map.Entry> entry : paths.entrySet()) { + String entryPoint = entry.getKey(); + List pathCounts = entry.getValue(); + + writer.write( + "\"" + + entryPoint + + "\"," + + pathCounts.stream() + .limit(3) + .map(Object::toString) + .collect(Collectors.joining(",")) + + "\n" + ); + } + } + } + } + + static class Row { + private final String entryPoint; + private final String method; + private String nodeColor = ""; + + private int instructionCovered = Integer.MIN_VALUE; + private int instructionMissed = Integer.MIN_VALUE; + private int branchesCovered = Integer.MIN_VALUE; + private int branchesMissed = Integer.MIN_VALUE; + private int linesCovered = Integer.MIN_VALUE; + private int linesMissed = Integer.MIN_VALUE; + private int complexityCovered = Integer.MIN_VALUE; + private int complexityMissed = Integer.MIN_VALUE; + private int methodCovered = Integer.MIN_VALUE; + private int methodMissed = Integer.MIN_VALUE; + + private boolean inJaCoCo = false; + private boolean inPrunedGraph = false; + + Row(String entryPoint, String method) { + this.entryPoint = entryPoint; + this.method = method; + } + + public void addJaCoCo(int instructionCovered, int instructionMissed, int branchesCovered, int branchesMissed, int linesCovered, int linesMissed, int complexityCovered, int complexityMissed, int methodCovered, int methodMissed) { + this.instructionCovered = instructionCovered; + this.instructionMissed = instructionMissed; + this.branchesCovered = branchesCovered; + this.branchesMissed = branchesMissed; + this.linesCovered = linesCovered; + this.linesMissed = linesMissed; + this.complexityCovered = complexityCovered; + this.complexityMissed = complexityMissed; + this.methodCovered = methodCovered; + this.methodMissed = methodMissed; + this.inJaCoCo = true; + } + + public void addPruned(String nodeColor) { + this.nodeColor = nodeColor; + this.inPrunedGraph = true; + } + + @Override + public String toString() { + return String.join(",", + "\"" + entryPoint + "\"", + "\"" + method + "\"", + "\"" + nodeColor + "\"", + + // stuff here + cntToStr(instructionCovered), + cntToStr(instructionMissed), + cntToStr(branchesCovered), + cntToStr(branchesMissed), + cntToStr(linesCovered), + cntToStr(linesMissed), + cntToStr(complexityCovered), + cntToStr(complexityMissed), + cntToStr(methodCovered), + cntToStr(methodMissed), + inJaCoCo ? "Y" : "N", + inPrunedGraph ? "Y" : "N" + ); + } + + private String cntToStr(int cnt) { + return cnt == Integer.MIN_VALUE ? "UNK" : String.valueOf(cnt); + } + } + + private static Options getOptions() { + Options options = new Options(); + options.addOption( + Option.builder(PROJECT_NAME) + .longOpt(PROJECT_NAME_LONG) + .hasArg(true) + .desc("[REQUIRED] specify the project name") + .required(true) + .build()); + + options.addOption( + Option.builder(OUTPUT_FILE_NAME) + .longOpt(OUTPUT_FILE_NAME_LONG) + .hasArg(true) + .desc("[OPTIONAL] specify the output filename (default to stdout)") + .required(false) + .build()); + + options.addOption( + Option.builder(OUTPUT_PATH_FILE_NAME) + .longOpt(OUTPUT_PATH_FILE_NAME_LONG) + .hasArg(true) + .desc("[OPTIONAL] specify the path count output filename (default to stdout)") + .required(false) + .build()); + + options.addOption( + Option.builder(ARTIFACT_TIMESTAMP) + .longOpt(ARTIFACT_TIMESTAMP_LONG) + .hasArg(true) + .desc("[OPTIONAL] specify the artifact timestamp (defaults to latest run in artifacts)") + .required(false) + .build()); + + + return options; + } + +} diff --git a/src/main/java/edu/uic/bitslab/callgraph/GetBest.java b/src/main/java/edu/uic/bitslab/callgraph/GetBest.java new file mode 100644 index 00000000..34fddea7 --- /dev/null +++ b/src/main/java/edu/uic/bitslab/callgraph/GetBest.java @@ -0,0 +1,330 @@ +package edu.uic.bitslab.callgraph; + +import gr.gousiosg.javacg.stat.JCallGraph; +import gr.gousiosg.javacg.stat.coverage.ColoredNode; +import gr.gousiosg.javacg.stat.graph.Utilities; +import org.jgrapht.Graph; +import org.jgrapht.GraphPath; +import org.jgrapht.alg.interfaces.ShortestPathAlgorithm; +import org.jgrapht.alg.shortestpath.BFSShortestPath; +import org.jgrapht.event.ConnectedComponentTraversalEvent; +import org.jgrapht.event.EdgeTraversalEvent; +import org.jgrapht.event.TraversalListenerAdapter; +import org.jgrapht.event.VertexTraversalEvent; +import org.jgrapht.graph.DefaultEdge; +import org.jgrapht.nio.Attribute; +import org.jgrapht.nio.DefaultAttribute; +import org.jgrapht.nio.dot.DOTExporter; +import org.jgrapht.traverse.DepthFirstIterator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.xml.bind.DatatypeConverter; +import java.io.*; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.*; +import java.util.stream.Collectors; + + +public class GetBest { + private static final Logger LOGGER = LoggerFactory.getLogger(GetBest.class); + private final Graph reachability; + private final String propertyName; + private final Map score = new HashMap<>(); + + /* Colors - Copied from gr.gousiosg.javacg.stat.coverage.ColoredNode */ + private static final String IMPLIED_COVERAGE_COLOR = "skyblue"; + private static final String LIGHT_GREEN = "greenyellow"; + private static final String MEDIUM_GREEN = "green1"; + private static final String MEDIUM_DARK_GREEN = "green3"; + private static final String DARK_GREEN = "green4"; + private static final String FIREBRICK = "lightpink"; + private static final String ENTRYPOINT_COLOR = "lightgoldenrod"; + private static final String NO_COLOR = "ghostwhite"; + private static final String TEST_NODE_COLOR = "plum"; + + private static final String DEFAULT_EDGE_COLOR = "black"; + private static final String FIRST_PATH_EDGE_COLOR = "green"; + private static final String SECOND_PATH_EDGE_COLOR = "yellow"; + private static final String THIRD_PATH_EDGE_COLOR = "red"; + + private static final int NUM_TOP_PATHS = 3; + + // color by type + private static final String UNCOVERED_COLOR = FIREBRICK; + + + + public static void main(String[] args) throws IOException, ClassNotFoundException { + String objectFileName = args[0]; + String propertyName; + + if (args.length == 2) { + propertyName = args[1]; + } else { + String filename = Path.of(args[0]).getFileName().toString(); + propertyName = filename.substring(0, filename.lastIndexOf('.')); + } + + LOGGER.info("----------GRAPH------------"); + LOGGER.info(objectFileName); + + GetBest o = new GetBest(objectFileName, propertyName); + o.run(); + } + + public GetBest(Graph reachability, String propertyName) { + this.reachability = reachability; + this.propertyName = propertyName; + } + + public GetBest(String objectFileName, String propertyName) throws IOException, ClassNotFoundException { + try (ObjectInput ois = new ObjectInputStream(new FileInputStream(objectFileName))) { + reachability = returnGraph(ois.readObject()); + } + this.propertyName = propertyName; + } + + @SuppressWarnings("unchecked") + private Graph returnGraph(Object o) { + if (o instanceof Graph) { + return (Graph) o; + } + + LOGGER.error("Expected instanceof Graph, but received " + o.getClass().getName() + " instead."); + return null; + } + + public void run() { + // we don't have a graph to reason about! + if (reachability == null) { + return; + } + + ColoredNode entryPoint = getEntryPoint(); + + if (entryPoint == null) { + LOGGER.error("Unable to find entry point"); + return; + } + + DepthFirstIterator iter = new DepthFirstIterator<>(reachability, entryPoint); + iter.addTraversalListener(new GetBestTraversalListener(reachability, score)); + + // traverse the graph to set the scores + while (iter.hasNext()) { + iter.next(); + } + + // get all the paths in best order + List pathWeights = new ArrayList<>(); + BFSShortestPath bfsShortestPath = new BFSShortestPath<>(reachability); + ShortestPathAlgorithm.SingleSourcePaths allPaths = bfsShortestPath.getPaths(entryPoint); + HashSet sinkSet = reachability.vertexSet().stream().filter(vertex -> reachability.outDegreeOf(vertex) == 0).collect(Collectors.toCollection(HashSet::new)); + sinkSet.forEach( sinkVertex -> { + GraphPath executionPath = allPaths.getPath(sinkVertex); + double pathSum = executionPath.getEdgeList().stream().map(reachability::getEdgeTarget).filter(Objects::nonNull).mapToDouble(score::get).sum(); + + if (pathSum > 0.00 && score.getOrDefault(executionPath.getEndVertex(), 0.00d) > 0.00) { + pathWeights.add(new PathWeight(executionPath, pathSum)); + } + }); + + + // output sorted paths + Comparator comparator = Comparator.comparingDouble(p -> p.weight); + pathWeights.sort(comparator.reversed()); + + String outputPaths = JCallGraph.OUTPUT_DIRECTORY + propertyName + "-paths.csv"; + try { + Writer writer = new FileWriter(outputPaths); + for(PathWeight pathWeight : pathWeights) { + String pathString = pathWeight.path + .getVertexList() + .stream() + .map(p -> '"' + p.toString() + '"') + .collect(Collectors.joining(",")); + + MessageDigest md = MessageDigest.getInstance("md5"); + md.update(pathString.getBytes()); + byte[] digest = md.digest(); + String pathHash = DatatypeConverter.printHexBinary(digest).toUpperCase(); + + writer.write( + pathWeight.weight + "," + + pathHash + "," + + pathString + + System.lineSeparator()); + } + + writer.close(); + } catch (IOException | NoSuchAlgorithmException e) { + LOGGER.error("Unable to write paths to " + outputPaths); + } + + String[] edgeStringColor = { + FIRST_PATH_EDGE_COLOR, + SECOND_PATH_EDGE_COLOR, + THIRD_PATH_EDGE_COLOR + }; + + Map> edgePathNumber = new HashMap<>(); + + // color the edges based on the top three paths + for (int pathIndex = 0; pathIndex < NUM_TOP_PATHS && pathIndex < pathWeights.size(); pathIndex++) { + for (DefaultEdge edge : pathWeights.get(pathIndex).path.getEdgeList()) { + if (edgePathNumber.containsKey(edge)) { + edgePathNumber.get(edge).add(pathIndex); + } else { + edgePathNumber.put(edge, new ArrayList<>(List.of(pathIndex))); + } + } + } + + /* annotated graph - Write to .dot file in output directory */ + String path = JCallGraph.OUTPUT_DIRECTORY + propertyName + "-annotated.dot"; + try { + Writer writer = new FileWriter(path); + DOTExporter exporter = Utilities.coloredExporter(); + exporter.setVertexAttributeProvider( + (v) -> { + Map map = new LinkedHashMap<>(); + map.put("label", DefaultAttribute.createAttribute(score.get(v) + " - " + dotFormat(v.toString()))); + map.put("style", DefaultAttribute.createAttribute("filled")); + map.put("fillcolor", DefaultAttribute.createAttribute(v.getColor())); + return map; + }); + exporter.setEdgeAttributeProvider( + (edge) -> { + Map map = new LinkedHashMap<>(); + + if (edgePathNumber.containsKey(edge)) { + List pathSet = edgePathNumber.get(edge); + + + int pathNumber = pathSet.get(0); + map.put("color", DefaultAttribute.createAttribute(edgeStringColor[pathNumber])); + + map.put("penwidth", DefaultAttribute.createAttribute(3.0)); + + String stringPathNumbers = pathSet.stream().map(String::valueOf).collect(Collectors.joining("/")); + map.put("label", DefaultAttribute.createAttribute("P" + stringPathNumbers)); + } + + return map; + } + ); + exporter.exportGraph(reachability, writer); + LOGGER.info("Graph written to " + path + "!"); + } catch (IOException e) { + LOGGER.error("Unable to write callgraph to " + path); + } + } + + private ColoredNode getEntryPoint() { + Set vertexSet = reachability.vertexSet(); + + for (ColoredNode v : vertexSet) { + if (reachability.inDegreeOf(v) == 0) { + return v; + } + } + + return null; + } + + private static String dotFormat(String vertex) { + return "\"" + vertex + "\""; + } + + static class PathWeight { + public final GraphPath path; + public final Double weight; + + PathWeight(GraphPath path, Double weight) { + this.path = path; + this.weight = weight; + } + } + + static class GetBestTraversalListener extends TraversalListenerAdapter { + Graph graph; + Map score; + + GetBestTraversalListener(Graph graph, Map score) { + super(); + + this.graph = graph; + this.score = score; + } + + @Override + public void connectedComponentFinished(ConnectedComponentTraversalEvent connectedComponentTraversalEvent) { + + } + + @Override + public void connectedComponentStarted(ConnectedComponentTraversalEvent connectedComponentTraversalEvent) { + + } + + @Override + public void edgeTraversed(EdgeTraversalEvent edgeTraversalEvent) { + + } + + @Override + public void vertexTraversed(VertexTraversalEvent vertexTraversalEvent) { + + } + + @Override + public void vertexFinished(VertexTraversalEvent vertexTraversalEvent) { + ColoredNode parentVertex = vertexTraversalEvent.getVertex(); + score.put(parentVertex, Score(parentVertex)); + } + + private double vertexColorToInt(String color) { + switch (color) { + case UNCOVERED_COLOR: + return 1.00; + + case LIGHT_GREEN: + return 0.00; + + case MEDIUM_GREEN: + return 0.00; + + case MEDIUM_DARK_GREEN: + return 0.00; + + case DARK_GREEN: + return 0.00; + + default: + return 0.00; + } + } + + private double Score(ColoredNode vertex) { + final double weightParentScore = 1; + final double weightChildrenScore = .5; + + // parent score (note: high score is better) + double parentScore = vertexColorToInt(vertex.getColor()); + + double totalChildrenScore = graph.outgoingEdgesOf(vertex) + .stream() + .mapToDouble( e -> score.getOrDefault(graph.getEdgeTarget(e), 0.00) ) + .sum(); + + double vertexScore = (weightParentScore * parentScore) + + (weightChildrenScore * totalChildrenScore); + + return Math.round(vertexScore * 100) / 100.0d; + } + } +} + diff --git a/src/main/java/gr/gousiosg/javacg/dyn/Instrumenter.java b/src/main/java/gr/gousiosg/javacg/dyn/Instrumenter.java index 2fbc698c..33c24f18 100644 --- a/src/main/java/gr/gousiosg/javacg/dyn/Instrumenter.java +++ b/src/main/java/gr/gousiosg/javacg/dyn/Instrumenter.java @@ -28,6 +28,8 @@ package gr.gousiosg.javacg.dyn; +import javassist.*; + import java.io.ByteArrayInputStream; import java.io.IOException; import java.lang.instrument.ClassFileTransformer; @@ -38,12 +40,6 @@ import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; -import javassist.CannotCompileException; -import javassist.ClassPool; -import javassist.CtBehavior; -import javassist.CtClass; -import javassist.NotFoundException; - public class Instrumenter implements ClassFileTransformer { static List pkgIncl = new ArrayList<>(); @@ -91,18 +87,24 @@ public static void premain(String argument, Instrumentation instrumentation) { } catch (PatternSyntaxException pse) { err("pattern: " + pattern + " not valid, ignoring"); } - if (argtype.equals("incl")) - pkgIncl.add(p); - else - pkgExcl.add(p); + if (argtype.equals("incl")) pkgIncl.add(p); + else pkgExcl.add(p); } } instrumentation.addTransformer(new Instrumenter()); } - public byte[] transform(ClassLoader loader, String className, Class clazz, - java.security.ProtectionDomain domain, byte[] bytes) { + private static void err(String msg) { + // System.err.println("[JAVACG-DYN] " + msg); + } + + public byte[] transform( + ClassLoader loader, + String className, + Class clazz, + java.security.ProtectionDomain domain, + byte[] bytes) { boolean enhanceClass = false; String name = className.replace("/", "."); @@ -167,15 +169,10 @@ private void enhanceMethod(CtBehavior method, String className) String name = className.substring(className.lastIndexOf('.') + 1, className.length()); String methodName = method.getName(); - if (method.getName().equals(name)) - methodName = ""; + if (method.getName().equals(name)) methodName = ""; - method.insertBefore("gr.gousiosg.javacg.dyn.MethodStack.push(\"" + className - + ":" + methodName + "\");"); + method.insertBefore( + "gr.gousiosg.javacg.dyn.MethodStack.push(\"" + className + ":" + methodName + "\");"); method.insertAfter("gr.gousiosg.javacg.dyn.MethodStack.pop();"); } - - private static void err(String msg) { - //System.err.println("[JAVACG-DYN] " + msg); - } -} \ No newline at end of file +} diff --git a/src/main/java/gr/gousiosg/javacg/dyn/MethodStack.java b/src/main/java/gr/gousiosg/javacg/dyn/MethodStack.java index 1da646dc..557f2ded 100644 --- a/src/main/java/gr/gousiosg/javacg/dyn/MethodStack.java +++ b/src/main/java/gr/gousiosg/javacg/dyn/MethodStack.java @@ -31,43 +31,42 @@ import java.io.File; import java.io.FileWriter; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Stack; +import java.util.*; public class MethodStack { - private static Stack stack = new Stack<>(); - private static Map, Integer> callgraph = new HashMap<>(); - static FileWriter fw; + static FileWriter fw; static StringBuffer sb; static long threadid = -1L; + private static Stack stack = new Stack<>(); + private static Map, Integer> callgraph = new HashMap<>(); static { - Runtime.getRuntime().addShutdownHook(new Thread() { - public void run() { - try { - fw.close(); - } catch (IOException e) { - e.printStackTrace(); - } - //Sort by number of calls - List> keys = new ArrayList<>(); - keys.addAll(callgraph.keySet()); - Collections.sort(keys, (o1, o2) -> { - Integer v1 = callgraph.get(o1); - Integer v2 = callgraph.get(o2); - return v1.compareTo(v2); - }); + Runtime.getRuntime() + .addShutdownHook( + new Thread() { + public void run() { + try { + fw.close(); + } catch (IOException e) { + e.printStackTrace(); + } + // Sort by number of calls + List> keys = new ArrayList<>(); + keys.addAll(callgraph.keySet()); + Collections.sort( + keys, + (o1, o2) -> { + Integer v1 = callgraph.get(o1); + Integer v2 = callgraph.get(o2); + return v1.compareTo(v2); + }); - for (Pair key : keys) { - System.out.println(key + " " + callgraph.get(key)); - } - } - }); + for (Pair key : keys) { + System.out.println(key + " " + callgraph.get(key)); + } + } + }); File log = new File("calltrace.txt"); try { fw = new FileWriter(log); @@ -78,18 +77,14 @@ public void run() { } public static void push(String callname) throws IOException { - if (threadid == -1) - threadid = Thread.currentThread().getId(); - - if (Thread.currentThread().getId() != threadid) - return; - + if (threadid == -1) threadid = Thread.currentThread().getId(); + + if (Thread.currentThread().getId() != threadid) return; + if (!stack.isEmpty()) { Pair p = new Pair<>(stack.peek(), callname); - if (callgraph.containsKey(p)) - callgraph.put(p, callgraph.get(p) + 1); - else - callgraph.put(p, 1); + if (callgraph.containsKey(p)) callgraph.put(p, callgraph.get(p) + 1); + else callgraph.put(p, 1); } sb.setLength(0); sb.append(">[").append(stack.size()).append("]"); @@ -100,11 +95,9 @@ public static void push(String callname) throws IOException { } public static void pop() throws IOException { - if (threadid == -1) - threadid = Thread.currentThread().getId(); + if (threadid == -1) threadid = Thread.currentThread().getId(); - if (Thread.currentThread().getId() != threadid) - return; + if (Thread.currentThread().getId() != threadid) return; String returnFrom = stack.pop(); sb.setLength(0); @@ -113,4 +106,4 @@ public static void pop() throws IOException { sb.append(returnFrom).append("=").append(System.nanoTime()).append("\n"); fw.write(sb.toString()); } -} \ No newline at end of file +} diff --git a/src/main/java/gr/gousiosg/javacg/dyn/Pair.java b/src/main/java/gr/gousiosg/javacg/dyn/Pair.java index c3315c1b..5a9bd10f 100644 --- a/src/main/java/gr/gousiosg/javacg/dyn/Pair.java +++ b/src/main/java/gr/gousiosg/javacg/dyn/Pair.java @@ -32,12 +32,12 @@ public class Pair { public A first; public B second; - + public Pair(A first, B second) { this.first = first; this.second = second; } - + @Override public String toString() { StringBuffer b = new StringBuffer(first.toString()); @@ -45,17 +45,14 @@ public String toString() { b.append(second); return b.toString(); } - + @Override public boolean equals(Object obj) { - if (obj == null) - return false; - if (obj == this) - return true; - if (obj.getClass() != getClass()) - return false; + if (obj == null) return false; + if (obj == this) return true; + if (obj.getClass() != getClass()) return false; - Pair p = (Pair)obj; + Pair p = (Pair) obj; return first.equals(p.first) && second.equals(p.second); } diff --git a/src/main/java/gr/gousiosg/javacg/stat/ClassVisitor.java b/src/main/java/gr/gousiosg/javacg/stat/ClassVisitor.java index 9e12d860..5182b9cf 100644 --- a/src/main/java/gr/gousiosg/javacg/stat/ClassVisitor.java +++ b/src/main/java/gr/gousiosg/javacg/stat/ClassVisitor.java @@ -38,23 +38,23 @@ import java.util.Set; /** - * The simplest of class visitors, invokes the method visitor class for each - * method found. + * The simplest of class visitors, invokes the method visitor class for each method found. */ public class ClassVisitor extends EmptyVisitor { + private final DynamicCallManager DCManager = new DynamicCallManager(); + private final JarMetadata jarMetadata; private JavaClass clazz; private ConstantPoolGen constants; private String classReferenceFormat; - private final DynamicCallManager DCManager = new DynamicCallManager(); - private Set> methodCalls = new HashSet<>(); - private final JarMetadata jarMetadata; + private boolean isTestClass; - public ClassVisitor(JavaClass jc, JarMetadata jarMetadata) { + public ClassVisitor(JavaClass jc, JarMetadata jarMetadata, boolean isTestClass) { clazz = jc; constants = new ConstantPoolGen(clazz.getConstantPool()); this.jarMetadata = jarMetadata; + this.isTestClass = isTestClass; classReferenceFormat = "C:" + clazz.getClassName() + " %s"; } @@ -72,18 +72,16 @@ public void visitJavaClass(JavaClass jc) { public void visitConstantPool(ConstantPool constantPool) { for (int i = 0; i < constantPool.getLength(); i++) { Constant constant = constantPool.getConstant(i); - if (constant == null) - continue; + if (constant == null) continue; if (constant.getTag() == 7) { - String referencedClass = - constantPool.constantToString(constant); + String referencedClass = constantPool.constantToString(constant); } } } public void visitMethod(Method method) { MethodGen mg = new MethodGen(method, clazz.getClassName(), constants); - MethodVisitor visitor = new MethodVisitor(mg, clazz, jarMetadata); + MethodVisitor visitor = new MethodVisitor(mg, clazz, jarMetadata, isTestClass); methodCalls.addAll(visitor.start()); } diff --git a/src/main/java/gr/gousiosg/javacg/stat/DynamicCallManager.java b/src/main/java/gr/gousiosg/javacg/stat/DynamicCallManager.java index de8ce3b9..1f0fde1c 100644 --- a/src/main/java/gr/gousiosg/javacg/stat/DynamicCallManager.java +++ b/src/main/java/gr/gousiosg/javacg/stat/DynamicCallManager.java @@ -23,43 +23,37 @@ import java.util.regex.Pattern; /** - * {@link DynamicCallManager} provides facilities to retrieve information about - * dynamic calls statically. - *

- * Most of the time, call relationships are explicit, which allows to properly - * build the call graph statically. But in the case of dynamic linking, i.e. - * invokedynamic instructions, this relationship might be unknown - * until the code is actually executed. Indeed, bootstrap methods are used to - * dynamically link the code at first call. One can read details about the - * invokedynamic - * instruction to know more about this mechanism. - *

- * Nested lambdas are particularly subject to such absence of concrete caller, - * which lead us to produce method names like lambda$null$0, which - * breaks the call graph. This information can however be retrieved statically - * through the code of the bootstrap method called. - *

- * In {@link #retrieveCalls(Method, JavaClass)}, we retrieve the (called, - * caller) relationships by analyzing the code of the caller {@link Method}. - * This information is then used in {@link #linkCalls(Method)} to rename the - * called {@link Method} properly. + * {@link DynamicCallManager} provides facilities to retrieve information about dynamic calls + * statically. + * + *

Most of the time, call relationships are explicit, which allows to properly build the call + * graph statically. But in the case of dynamic linking, i.e. invokedynamic + * instructions, this relationship might be unknown until the code is actually executed. Indeed, + * bootstrap methods are used to dynamically link the code at first call. One can read details about + * the + * invokedynamic instruction to know more about this mechanism. + * + *

Nested lambdas are particularly subject to such absence of concrete caller, which lead us to + * produce method names like lambda$null$0, which breaks the call graph. This + * information can however be retrieved statically through the code of the bootstrap method called. + * + *

In {@link #retrieveCalls(Method, JavaClass)}, we retrieve the (called, caller) relationships + * by analyzing the code of the caller {@link Method}. This information is then used in {@link + * #linkCalls(Method)} to rename the called {@link Method} properly. * * @author Matthieu Vergne */ public class DynamicCallManager { - private static Logger LOGGER = LoggerFactory.getLogger(DynamicCallManager.class); - - private static final Pattern BOOTSTRAP_CALL_PATTERN = Pattern - .compile("invokedynamic\t(\\d+):\\S+ \\S+ \\(\\d+\\)"); + private static final Pattern BOOTSTRAP_CALL_PATTERN = + Pattern.compile("invokedynamic\t(\\d+):\\S+ \\S+ \\(\\d+\\)"); private static final int CALL_HANDLE_INDEX_ARGUMENT = 1; - + private static Logger LOGGER = LoggerFactory.getLogger(DynamicCallManager.class); private final Map dynamicCallers = new HashMap<>(); /** - * Retrieve dynamic call relationships based on the code of the provided - * {@link Method}. + * Retrieve dynamic call relationships based on the code of the provided {@link Method}. * * @param method {@link Method} to analyze the code * @param jc {@link JavaClass} info, which contains the bootstrap methods @@ -92,7 +86,8 @@ public void retrieveCalls(Method method, JavaClass jc) { private String getMethodNameFromHandleIndex(ConstantPool cp, int callIndex) { ConstantMethodHandle handle = (ConstantMethodHandle) cp.getConstant(callIndex); ConstantCP ref = (ConstantCP) cp.getConstant(handle.getReferenceIndex()); - ConstantNameAndType nameAndType = (ConstantNameAndType) cp.getConstant(ref.getNameAndTypeIndex()); + ConstantNameAndType nameAndType = + (ConstantNameAndType) cp.getConstant(ref.getNameAndTypeIndex()); return nameAndType.getName(cp); } diff --git a/src/main/java/gr/gousiosg/javacg/stat/GraphUtils.java b/src/main/java/gr/gousiosg/javacg/stat/GraphUtils.java deleted file mode 100644 index 83cd5a38..00000000 --- a/src/main/java/gr/gousiosg/javacg/stat/GraphUtils.java +++ /dev/null @@ -1,400 +0,0 @@ -package gr.gousiosg.javacg.stat; - -import gr.gousiosg.javacg.dyn.Pair; -import gr.gousiosg.javacg.stat.support.IgnoredConstants; -import gr.gousiosg.javacg.stat.support.JarMetadata; -import gr.gousiosg.javacg.stat.support.coverage.ColoredNode; -import org.apache.bcel.classfile.ClassParser; -import org.jgrapht.Graph; -import org.jgrapht.graph.DefaultDirectedGraph; -import org.jgrapht.graph.DefaultEdge; -import org.jgrapht.nio.Attribute; -import org.jgrapht.nio.DefaultAttribute; -import org.jgrapht.nio.dot.DOTExporter; -import org.reflections.Reflections; -import org.reflections.scanners.SubTypesScanner; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.*; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLClassLoader; -import java.util.*; -import java.util.function.Function; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -/** - * Provides graph utilities such as: - * - Building a graph ({@link GraphUtils#buildGraph(Map)}) - * - Finding the reachability in a graph ({@link GraphUtils#reachability(Graph, String, Optional)}) - * - Finding the ancestry in a graph ({@link GraphUtils#ancestry(Graph, String, int)}) - */ -public class GraphUtils { - - private static final Logger LOGGER = LoggerFactory.getLogger(GraphUtils.class); - - private static final String LABEL = "label"; - private static final String STYLE = "style"; - private static final String FILLCOLOR = "fillcolor"; - private static final String FILLED = "filled"; - private static final String NODE_DELIMITER = "\""; - - public static Graph reachability(Graph graph, String entrypoint, Optional maybeMaximumDepth) { - - if (!graph.containsVertex(entrypoint)) { - LOGGER.error("---> " + entrypoint + "<---"); - LOGGER.error("The graph doesn't contain the vertex specified as the entry point!"); - throw new InputMismatchException("graph doesn't contain vertex " + entrypoint); - } - - if (maybeMaximumDepth.isPresent() && (maybeMaximumDepth.get() < 0)) { - LOGGER.error("Depth " + maybeMaximumDepth.get() + " must be greater than 0!"); - System.exit(1); - } - - LOGGER.info("Starting reachability at entry point: " + entrypoint); - maybeMaximumDepth.ifPresent(d -> LOGGER.info("Traversing to depth " + d)); - - Graph subgraph = new DefaultDirectedGraph<>(DefaultEdge.class); - int currentDepth = 0; - - Deque reachable = new ArrayDeque<>(); - reachable.push(entrypoint); - - Map subgraphNodes = new HashMap<>(); - Set seenBefore = new HashSet<>(); - Set nextLevel = new HashSet<>(); - - while (!reachable.isEmpty()) { - - /* Stop once we've surpassed maximum depth */ - if (maybeMaximumDepth.isPresent() && (maybeMaximumDepth.get() < currentDepth)) { - break; - } - - while (!reachable.isEmpty()) { - /* Visit reachable node */ - String source = reachable.pop(); - ColoredNode sourceNode = subgraphNodes.containsKey(source) ? subgraphNodes.get(source) : new ColoredNode(source); - - /* Keep track of who we've visited */ - seenBefore.add(source); - if (!subgraphNodes.containsKey(source)) { - subgraph.addVertex(sourceNode); - subgraphNodes.put(source, sourceNode); - } - - /* Check if we can add deeper edges or not */ - if (maybeMaximumDepth.isPresent() && (maybeMaximumDepth.get() == currentDepth)) { - break; - } - - graph.edgesOf(source).forEach(edge -> { - String target = graph.getEdgeTarget(edge); - ColoredNode targetNode = subgraphNodes.containsKey(target) ? subgraphNodes.get(target) : new ColoredNode(target); - - if (!subgraphNodes.containsKey(target)) { - subgraphNodes.put(target, targetNode); - subgraph.addVertex(targetNode); - } - - if (graph.containsEdge(source, target) && !subgraph.containsEdge(sourceNode, targetNode)) { - subgraph.addEdge(sourceNode, targetNode); - } - - /* Have we visited this vertex before? */ - if (!seenBefore.contains(target)) { - nextLevel.add(target); - seenBefore.add(target); - } - - }); - } - - currentDepth++; - reachable.addAll(nextLevel); - nextLevel.clear(); - } - - subgraphNodes.get(entrypoint).markEntryPoint(); - return subgraph; - } - - public static Graph ancestry(Graph graph, String entrypoint, int ancestryDepth) { - - if (!graph.containsVertex(entrypoint)) { - LOGGER.error("---> " + entrypoint + "<---"); - LOGGER.error("The graph doesn't contain the vertex specified as the entry point!"); - throw new InputMismatchException("graph doesn't contain vertex " + entrypoint); - } - - LOGGER.info("Starting ancestry at entry point: " + entrypoint); - LOGGER.info("Traversing to depth " + ancestryDepth); - - /* Book-keeping */ - Graph ancestry = new DefaultDirectedGraph<>(DefaultEdge.class); - Map nodeMap = new HashMap<>(); - Deque parentsToInspect = new ArrayDeque<>(); - Set seenBefore = new HashSet<>(); - Set nextLevel = new HashSet<>(); - - /* Add root node to ancestry graph */ - ColoredNode root = new ColoredNode(entrypoint); - ancestry.addVertex(root); - nodeMap.put(entrypoint, root); - parentsToInspect.push(entrypoint); - - int currentDepth = 0; - while (!parentsToInspect.isEmpty()) { - - if (ancestryDepth < currentDepth) { - break; - } - - /* Loop over all nodes that we haven't yet seen yet and are reachable at depth "currentDepth" */ - while (!parentsToInspect.isEmpty()) { - - /* Fetch next node */ - String child = parentsToInspect.pop(); - ColoredNode childNode = nodeMap.containsKey(child) ? nodeMap.get(child) : new ColoredNode(child); - - /* Keep track of who we've seen before */ - seenBefore.add(child); - if (!nodeMap.containsKey(child)) { - ancestry.addVertex(childNode); - nodeMap.put(child, childNode); - } - - graph.incomingEdgesOf(child).forEach(incomingEdge -> { - String parent = graph.getEdgeSource(incomingEdge); - ColoredNode parentNode = nodeMap.containsKey(parent) ? nodeMap.get(parent) : new ColoredNode(parent); - - if (!nodeMap.containsKey(parent)) { - nodeMap.put(parent, parentNode); - ancestry.addVertex(parentNode); - } - - ancestry.addEdge(parentNode, childNode); - - /* Have we visited this vertex before? */ - if (!seenBefore.contains(parent)) { - nextLevel.add(parent); - seenBefore.add(parent); - } - }); - } - - currentDepth++; - parentsToInspect.addAll(nextLevel); - nextLevel.clear(); - } - - nodeMap.get(entrypoint).markEntryPoint(); - return ancestry; - } - - public static void writeGraph(Graph graph, DOTExporter exporter, Optional maybeOutputName) { - LOGGER.info("Attempting to store callgraph..."); - - if (maybeOutputName.isEmpty()) { - LOGGER.error("No output name specified!"); - return; - } - - /* Write to .dot file in output directory */ - String path = JCallGraph.OUTPUT_DIRECTORY + maybeOutputName.get(); - try { - Writer writer = new FileWriter(path); - exporter.exportGraph(graph, writer); - LOGGER.info("Graph written to " + path + "!"); - } catch (IOException e) { - LOGGER.error("Unable to write callgraph to " + path); - } - } - - public static Graph staticCallgraph(List> jars) throws InputMismatchException { - LOGGER.info("Beginning callgraph analysis..."); - - /* Load JAR URLs */ - List urls = new ArrayList<>(); - try { - for (Pair pair : jars) { - URL url = new URL("jar:file:" + pair.first + "!/"); - urls.add(url); - } - } catch (MalformedURLException e) { - LOGGER.error("Error loading URLs: " + e.getMessage()); - throw new InputMismatchException("Couldn't load provided JARs"); - } - - if (urls.isEmpty()) { - LOGGER.error("No URLs to scan!"); - throw new InputMismatchException("There are no URLs to scan!"); - } - - /* Setup infrastructure for analysis */ - URLClassLoader cl = URLClassLoader.newInstance(urls.toArray(new URL[0]), ClassLoader.getSystemClassLoader()); - Reflections reflections = new Reflections(cl, new SubTypesScanner(false)); - JarMetadata jarMetadata = new JarMetadata(cl, reflections); - - /* Store method calls (caller -> receiver) */ - Map> calls = new HashMap<>(); - - for (Pair pair : jars) { - String jarPath = pair.first; - File file = pair.second; - - try (JarFile jarFile = new JarFile(file)) { - LOGGER.info("Analyzing: " + jarFile.getName()); - Stream entries = enumerationAsStream(jarFile.entries()); - - Function getClassVisitor = - (ClassParser cp) -> { - try { - return new ClassVisitor(cp.parse(), jarMetadata); - } catch (IOException e) { - throw new UncheckedIOException(e); - } - }; - - /* Analyze each jar entry to find callgraph */ - entries.flatMap(e -> { - if (e.isDirectory() || !e.getName().endsWith(".class")) - return Stream.of(); - - /* Ignore specified JARs */ - if (shouldIgnoreEntry(e.getName().replace("/", "."))) { - return Stream.of(); - } else { - LOGGER.info("Inspecting " + e.getName()); - } - - ClassParser cp = new ClassParser(jarPath, e.getName()); - return getClassVisitor.apply(cp).start().methodCalls().stream(); - }).forEach(p -> { - /* Create edges between nodes */ - calls.putIfAbsent((p.first), new HashSet<>()); - calls.get(p.first).add(p.second); - }); - - } catch (IOException e) { - LOGGER.error("Error when analyzing JAR \"" + jarPath + "\": + e.getMessage()"); - e.printStackTrace(); - } - } - - /* Convert calls into a graph */ - Graph graph = buildGraph(calls); - - /* Prune bridge methods from graph */ - jarMetadata.getBridgeMethods().forEach(bridgeMethod -> { - - /* Fetch the bridge method and make sure it has exactly one outgoing edge */ - String bridgeNode = formatNode(bridgeMethod); - Optional maybeEdge = graph.outgoingEdgesOf(bridgeNode).stream().findFirst(); - - if (graph.outDegreeOf(bridgeNode) != 1 || maybeEdge.isEmpty()) { - - graph.outgoingEdgesOf(bridgeNode).stream().forEach(e -> { - LOGGER.error("\t" + graph.getEdgeSource(e) + " -> " + graph.getEdgeTarget(e)); - }); - LOGGER.error("Found a bridge method that doesn't have exactly 1 outgoing edge: " + bridgeMethod + " : " + graph.outDegreeOf(bridgeNode)); - System.exit(1); - } - - /* Fetch the bridge method's target */ - String bridgeTarget = graph.getEdgeTarget(maybeEdge.get()); - - /* Redirect all edges from the bridge method to its target */ - graph.incomingEdgesOf(bridgeNode).forEach(edge -> { - String sourceNode = graph.getEdgeSource(edge); - graph.addEdge(sourceNode, bridgeTarget); - }); - - /* Remove the bridge method from the graph */ - graph.removeVertex(bridgeNode); - }); - - return graph; - } - - private static Graph buildGraph(Map> methodCalls) throws InputMismatchException { - if (methodCalls.keySet().isEmpty()) { - throw new InputMismatchException("There is no call graph to look at!"); - } - - /* initialize the graph */ - Graph graph = new DefaultDirectedGraph<>(DefaultEdge.class); - - /* fill the graph with vertices and edges */ - methodCalls.keySet().forEach(source -> { - String sourceNode = formatNode(source); - putIfAbsent(graph, sourceNode); - - methodCalls.get(source).forEach(destination -> { - String destinationNode = formatNode(destination); - putIfAbsent(graph, destinationNode); - graph.addEdge(sourceNode, destinationNode); - }); - }); - - return graph; - } - - private static void putIfAbsent(Graph graph, String vertex) { - if (!graph.containsVertex(vertex)) { - graph.addVertex(vertex); - } - } - - private static boolean shouldIgnoreEntry(String entry) { - return IgnoredConstants.IGNORED_CALLING_PACKAGES.stream() - .anyMatch(entry::startsWith); - } - - public static String formatNode(String node) { - return NODE_DELIMITER + node + NODE_DELIMITER; - } - - public static Stream enumerationAsStream(Enumeration e) { - return StreamSupport.stream( - Spliterators.spliteratorUnknownSize( - new Iterator() { - public T next() { - return e.nextElement(); - } - - public boolean hasNext() { - return e.hasMoreElements(); - } - }, - Spliterator.ORDERED), false); - } - - public static DOTExporter defaultExporter() { - DOTExporter exporter = new DOTExporter<>(id -> id); - exporter.setVertexAttributeProvider((v) -> { - Map map = new LinkedHashMap<>(); - map.put(LABEL, DefaultAttribute.createAttribute(v)); - return map; - }); - return exporter; - } - - public static DOTExporter coloredExporter() { - DOTExporter exporter = new DOTExporter<>(ColoredNode::getLabel); - exporter.setVertexAttributeProvider((v) -> { - Map map = new LinkedHashMap<>(); - map.put(LABEL, DefaultAttribute.createAttribute(v.getLabel())); - map.put(STYLE, DefaultAttribute.createAttribute(FILLED)); - map.put(FILLCOLOR, DefaultAttribute.createAttribute(v.getColor())); - return map; - }); - return exporter; - } - -} diff --git a/src/main/java/gr/gousiosg/javacg/stat/JCallGraph.java b/src/main/java/gr/gousiosg/javacg/stat/JCallGraph.java index 38818d5f..c8bacbaf 100644 --- a/src/main/java/gr/gousiosg/javacg/stat/JCallGraph.java +++ b/src/main/java/gr/gousiosg/javacg/stat/JCallGraph.java @@ -28,123 +28,535 @@ package gr.gousiosg.javacg.stat; -import gr.gousiosg.javacg.stat.support.Arguments; -import gr.gousiosg.javacg.stat.support.coverage.ColoredNode; -import gr.gousiosg.javacg.stat.support.coverage.CoverageStatistics; -import gr.gousiosg.javacg.stat.support.coverage.JacocoCoverage; +import edu.uic.bitslab.callgraph.GetBest; +import gr.gousiosg.javacg.dyn.Pair; +import gr.gousiosg.javacg.stat.coverage.ColoredNode; +import gr.gousiosg.javacg.stat.coverage.CoverageStatistics; +import gr.gousiosg.javacg.stat.coverage.JacocoCoverage; +import gr.gousiosg.javacg.stat.graph.*; +import gr.gousiosg.javacg.stat.support.BuildArguments; +import gr.gousiosg.javacg.stat.support.GitArguments; +import gr.gousiosg.javacg.stat.support.RepoTool; +import gr.gousiosg.javacg.stat.support.TestArguments; +import org.apache.bcel.classfile.ClassParser; +import org.apache.bcel.classfile.JavaClass; +import org.apache.bcel.classfile.Method; +import org.apache.bcel.generic.Type; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.JGitInternalException; import org.jgrapht.Graph; import org.jgrapht.graph.DefaultEdge; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.xml.sax.SAXException; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; import javax.xml.bind.JAXBException; import javax.xml.parsers.ParserConfigurationException; -import java.io.IOException; -import java.util.InputMismatchException; -import java.util.Optional; +import java.io.*; +import java.util.*; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; + +import static java.util.Map.entry; /** - * Constructs a callgraph out of a JAR archive. Can combine multiple archives - * into a single call graph. + * Constructs a callgraph out of a JAR archive. Can combine multiple archives into a single call + * graph. * * @author Georgios Gousios * @author Will Cygan + * @author Alekh Meka */ public class JCallGraph { - private static final Logger LOGGER = LoggerFactory.getLogger(JCallGraph.class); - - public static final String OUTPUT_DIRECTORY = "./output/"; - private static final String REACHABILITY = "reachability"; - private static final String COVERAGE = "coverage"; - private static final String ANCESTRY = "ancestry"; - private static final String DELIMITER = "-"; - private static final String DOT_SUFFIX = ".dot"; - private static final String CSV_SUFFIX = ".csv"; - - public static void main(String[] args) { - try { - LOGGER.info("Starting java-cg!"); - Arguments arguments = new Arguments(args); - Graph graph = GraphUtils.staticCallgraph(arguments.getJars()); - JacocoCoverage jacocoCoverage = new JacocoCoverage(arguments.maybeCoverage()); - - /* Should we store the graph in a file? */ - if (arguments.maybeOutput().isPresent()) { - GraphUtils.writeGraph(graph, GraphUtils.defaultExporter(), arguments.maybeOutput().map(JCallGraph::asDot)); + public static final String OUTPUT_DIRECTORY = "./output/"; + private static final Logger LOGGER = LoggerFactory.getLogger(JCallGraph.class); + private static final String REACHABILITY = "reachability"; + private static final String COVERAGE = "coverage"; + private static final String ANCESTRY = "ancestry"; + private static final String DELIMITER = "-"; + private static final String DOT_SUFFIX = ".dot"; + private static final String CSV_SUFFIX = ".csv"; + private static final String SER_SUFFIX = ".ser"; + + public static void main(String[] args) { + try { + LOGGER.info("Starting java-cg!"); + switch(args[0]){ + case "manual-test": { + manualMain(args); + return; + } + case "git":{ + GitArguments arguments = new GitArguments(args); + RepoTool rt = maybeObtainTool(arguments); + rt.cloneRepo(); + rt.applyPatch(); + rt.buildJars(); + break; + } + case "build": { + // Build and serialize a staticcallgraph object with jar files provided + BuildArguments arguments = new BuildArguments(args); + StaticCallgraph callgraph = StaticCallgraph.build(arguments); + callgraph.JarEntry=arguments.getJars().get(0).first; + maybeSerializeStaticCallGraph(callgraph, arguments); + break; + } + case "buildyaml":{ + DumperOptions options = new DumperOptions(); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + options.setPrettyFlow(true); + Yaml yaml = new Yaml(options); + + JarInputStream jarFileStream = new JarInputStream(new FileInputStream(args[1])); + JarFile jarFile = new JarFile(args[1]); + + ArrayList listOfAllClasses = getAllClassesFromJar(jarFileStream); + ArrayList> nameEntryList = new ArrayList<>(); + for (JarEntry entry : listOfAllClasses) + nameEntryList.addAll(fetchAllMethodSignaturesForyaml(jarFile,entry)); + ArrayList> entryResult = new ArrayList<>(); + + for(Pair entry : nameEntryList) + entryResult.add(Map.ofEntries(entry("name",entry.first),entry("entryPoint",entry.second))); + + Map>> dataMap = new HashMap<>(); + dataMap.put("properties",entryResult); + final FileWriter writer = new FileWriter(args[2]+".yaml"); + yaml.dump(dataMap, writer); + break; + } + case "test": { + TestArguments arguments = new TestArguments(args); + String entryPoint = null; + // 1. Run Tests and obtain coverage + RepoTool rt = maybeObtainTool(arguments); + StaticCallgraph callgraph = deserializeStaticCallGraph(arguments); + List> coverageFilesAndEntryPointsShorthand = rt.obtainCoverageFilesAndEntryPoints(); + List> coverageFilesAndEntryPoints=new ArrayList<>(); + for(Pair s : coverageFilesAndEntryPointsShorthand) { + Pair result=new Pair<>(s.first,null); + if(s.second instanceof String){ + entryPoint= (String) s.second; } + else if(s.second instanceof ArrayList){ + try { + Optional returnType = Optional.empty(); + if(((ArrayList) s.second).size() > 1) + returnType = Optional.of(((ArrayList) s.second).get(1).toString()); - /* Should we compute reachability from the entry point? */ - if (arguments.maybeEntryPoint().isPresent()) { - inspectReachability(graph, arguments, jacocoCoverage, arguments.maybeEntryPoint().get()); + // Seventh argument, optional, parameter types of expected method + Optional paramterTypes = Optional.empty(); + if(((ArrayList) s.second).size() > 2) + paramterTypes = Optional.of(((ArrayList) s.second).get(2).toString()); + + entryPoint = generateEntryPoint(callgraph.JarEntry, (String) ((ArrayList) s.second).get(0), returnType, paramterTypes); + } catch(IOException e){ + LOGGER.error("Could not generate method signature", e); + } } + LOGGER.info("Entry point inferred for name \n"+s.first.substring(s.first.lastIndexOf("/")+1)+" is\n "+entryPoint); + result.second=entryPoint; + coverageFilesAndEntryPoints.add(result); + } + + for(Pair s : coverageFilesAndEntryPoints) { + // 2. For each coverage file we start with a fresh deserialized callgraph + callgraph = deserializeStaticCallGraph(arguments); + LOGGER.info("----------PROPERTY------------"); + String propertyName = s.first.substring(s.first.lastIndexOf("/") + 1, s.first.length() - 4); + LOGGER.info(propertyName); + rt.testProperty(propertyName); + JacocoCoverage jacocoCoverage = new JacocoCoverage(s.first); + // 3. Prune the graph with coverage + Pruning.pruneOriginalGraph(callgraph, jacocoCoverage); + // 4. Operate on the graph and write it to output + maybeWriteGraph(callgraph.graph, JCallGraph.OUTPUT_DIRECTORY + propertyName); + Graph prunedReachability = maybeInspectReachability(callgraph, arguments.maybeDepth(), jacocoCoverage, s.second, JCallGraph.OUTPUT_DIRECTORY + propertyName); + maybeInspectAncestry(callgraph, arguments, jacocoCoverage, Optional.of(s.second), Optional.of(propertyName)); - /* Should we compute ancestry from the entry point? */ - if (arguments.maybeAncestry().isPresent()) { - inspectAncestry(graph, arguments, jacocoCoverage, arguments.maybeEntryPoint().get(), arguments.maybeAncestry().get()); + try { + // write the best paths and annotated dot file + GetBest getBest = new GetBest(prunedReachability, propertyName); + getBest.run(); + } catch (NullPointerException e) { + // ok ... getbest blew up ... log it an continue + LOGGER.error("Get Best Null Pointer Exception: " + Arrays.stream(e.getStackTrace()).map(StackTraceElement::toString)); } - } catch (InputMismatchException e) { - LOGGER.error("Unable to load callgraph: " + e.getMessage()); - System.exit(1); - } catch (ParserConfigurationException | SAXException | JAXBException |IOException e) { - LOGGER.error("Error fetching Jacoco coverage"); - System.exit(1); + try { + rt.moveOutput(); + } catch (Exception exception) { + LOGGER.error("Error moving output to artifact: " + exception.getMessage()); + System.exit(1); + } + rt.cleanTarget(); + } + break; } - LOGGER.info("java-cg is finished! Enjoy!"); + default: + LOGGER.error("Invalid argument provided!"); + System.exit(1); + } + } catch (InputMismatchException e) { + LOGGER.error("Unable to load callgraph: " + e.getMessage()); + System.exit(1); + } catch(JGitInternalException e){ + LOGGER.error("Cloned directory already exists!"); + System.exit(1); + } catch(FileNotFoundException e){ + LOGGER.error("Error obtaining valid yaml folder path: " + e.getMessage()); + System.exit(1); + } catch (ParserConfigurationException | SAXException | JAXBException | IOException e) { + LOGGER.error("Error fetching Jacoco coverage: " + e.getMessage()); + System.exit(1); + } catch(ClassNotFoundException e){ + LOGGER.error("Error creating class through deserialization"); + System.exit(1); + } catch (GitAPIException e) { + LOGGER.error("Error cloning repository"); + System.exit(1); + } catch (InterruptedException e) { + LOGGER.error("Interrupted during applying patches/building jars"); + System.exit(1); } - public static void inspectReachability(Graph graph, Arguments arguments, JacocoCoverage jacocoCoverage, String entryPoint) { - /* Fetch reachability */ - Graph reachability = GraphUtils.reachability(graph, entryPoint, arguments.maybeDepth()); + LOGGER.info("java-cg is finished! Enjoy!"); - /* Apply coverage */ - jacocoCoverage.applyCoverage(reachability); + } - /* Should we write the graph to a file? */ - Optional outputName = arguments.maybeOutput().isPresent() - ? Optional.of(arguments.maybeOutput().get() + DELIMITER + REACHABILITY) - : Optional.empty(); + //Main function to convert class.method arg and generate its respective method signature + public static String generateEntryPoint(String jarPath, String shortName, Optional returnType, Optional parameterTypes) throws IOException { + JarFile jarFile = new JarFile(jarPath); + JarInputStream jarFileStream = new JarInputStream(new FileInputStream(jarPath)); - /* Attach depth to name if present */ - outputName = outputName.map(name -> { - if (arguments.maybeDepth().isPresent()) { - return name + DELIMITER + arguments.maybeDepth().get(); - } else { - return name; - } - }); + String methodName = shortName.substring(shortName.lastIndexOf('.') + 1); + String className = shortName.substring(0, shortName.lastIndexOf('.')); + ArrayList listOfFilteredClasses = getAllClassesFromJar(jarFileStream); + className = className.replaceAll("\\.", "/") + ".class"; - /* Store reachability in file? */ - if (outputName.isPresent()) { - GraphUtils.writeGraph(reachability, GraphUtils.coloredExporter(), outputName.map(JCallGraph::asDot)); - } + listOfFilteredClasses = getFilteredClassesFromJar(listOfFilteredClasses, className); - /* Analyze reachability coverage? */ - if (jacocoCoverage.hasCoverage()) { - CoverageStatistics.analyze(reachability, outputName.map(name -> asCsv(name + DELIMITER + COVERAGE))); - } + if(listOfFilteredClasses.size() > 1) { + LOGGER.error("Multiple class instances found as listed below:- "); + for(JarEntry entry : listOfFilteredClasses) + LOGGER.error(entry.getName()); + System.exit(1); + } + if(listOfFilteredClasses.size()==0){ + LOGGER.error("no class instances found "); + System.exit(1); } - public static void inspectAncestry(Graph graph, Arguments arguments, JacocoCoverage jacocoCoverage, String entryPoint, int ancestryDepth) { - Graph ancestry = GraphUtils.ancestry(graph, entryPoint, ancestryDepth); - jacocoCoverage.applyCoverage(ancestry); + return fetchMethodSignatures(jarFile, listOfFilteredClasses.get(0), methodName, returnType, parameterTypes); + + } + + //Fetch JarEntry of all classes in a Jan using JarInputStream + public static ArrayList getAllClassesFromJar(JarInputStream JarInputStream) throws IOException { + JarEntry jar; + ArrayList listOfAllClasses = new ArrayList<>(); + while(true) { + jar = JarInputStream.getNextJarEntry(); + if(jar == null) + break; + if((jar.getName().endsWith(".class"))) + listOfAllClasses.add(jar); + } + return listOfAllClasses; + } - /* Should we store the ancestry in a file? */ - if (arguments.maybeOutput().isPresent()) { - String subgraphOutputName = arguments.maybeOutput().get() + DELIMITER + ANCESTRY + DELIMITER + ancestryDepth; - GraphUtils.writeGraph(ancestry, GraphUtils.coloredExporter(), Optional.of(asDot(subgraphOutputName))); + //Fetch filtered classes from a list of JarEntry + public static ArrayList getFilteredClassesFromJar(ArrayList listOfAllClasses, String className) { + ArrayList listOfFilteredClasses= new ArrayList<>(); + for (JarEntry entry : listOfAllClasses) + if (entry.getName().endsWith(className)) + listOfFilteredClasses.add(entry); + return listOfFilteredClasses; + } +public static ArrayList> fetchAllMethodSignaturesForyaml (JarFile JarFile,JarEntry jar) throws IOException { + ClassParser cp = new ClassParser(JarFile.getInputStream(jar), jar.getName()); + JavaClass jc = cp.parse(); + + Method[] methods = jc.getMethods(); + String className =jc.getClassName().substring(jc.getClassName().lastIndexOf(".")+1); + ArrayList> signatureResults = new ArrayList<>(); + for(Method tempMethod : methods) + if(Arrays.stream(tempMethod.getAnnotationEntries()) + .map(e->e.getAnnotationType()) + .anyMatch(e->e.equals("Lcom/pholser/junit/quickcheck/Property;"))){ // .anyMatch(e->e.equals("Lorg/junit/Test;"))){ + String methodDescriptor=tempMethod.getName() + tempMethod.getSignature(); + signatureResults.add(new Pair<>(className+"#"+tempMethod.getName(),jc.getClassName() + "." + methodDescriptor)); + } + return signatureResults; +} + //Fetch the method signature of a method from a JarEntry + public static String fetchMethodSignatures(JarFile JarFile, JarEntry jar, String methodName, Optional returnType, Optional paramterTypes) throws IOException { + ClassParser cp = new ClassParser(JarFile.getInputStream(jar), jar.getName()); + JavaClass jc = cp.parse(); + + Method[] methods = jc.getMethods(); + ArrayList signatureResults = new ArrayList<>(); + + for(Method tempMethod : methods) + if(tempMethod.getName().equals(methodName)) + signatureResults.add(tempMethod); + + if(returnType.isPresent()) { + ArrayList tempsignatureResults = new ArrayList<>(); + for(Method tempMethod : signatureResults) + if(tempMethod.getReturnType().toString().contains(returnType.get())) + tempsignatureResults.add(tempMethod); + signatureResults=new ArrayList<>(tempsignatureResults); + + if(paramterTypes.isPresent()) { + String[] paramlist = paramterTypes.get().split(","); + for(Method tempMethod : signatureResults) + if(Arrays.equals(paramlist, Arrays.stream(tempMethod.getArgumentTypes()) + .map(Type::toString) + .map(e -> e.substring(e.lastIndexOf(".") + 1)) + .toArray())) + return jc.getClassName() + "." + tempMethod.getName() + tempMethod.getSignature(); + } + validateMethodList(signatureResults); + return jc.getClassName() + "." + signatureResults.get(0).getName() + signatureResults.get(0).getSignature(); + } else { + validateMethodList(signatureResults); + return jc.getClassName() + "." + signatureResults.get(0).getName() + signatureResults.get(0).getSignature(); + } + } + + // Check the size of list and submit Logger info for the methods + public static void validateMethodList(ArrayList methodList) { + if(methodList.size() > 1) { + LOGGER.error("Multiple overloaded methods for the given method name"); + for(Method method : methodList) { + LOGGER.info("Name:- " + method.getName() + " Return Type:- " + method.getReturnType().toString()); + LOGGER.info("Parameter Types:- "); + for(Type t : method.getArgumentTypes()) { + LOGGER.info(t.toString()); } + method.getArgumentTypes(); + } + System.exit(1); + } else if(methodList.size() == 0) { + LOGGER.info("Incorrect arguments supplied"); + System.exit(1); } + } + + public static void manualMain(String[] args) { - private static String asDot(String name) { - return name.endsWith(DOT_SUFFIX) ? name : (name + DOT_SUFFIX); + // First argument: the serialized file + StaticCallgraph callgraph = null; + try { + File f = new File(args[1]); + LOGGER.info("Deserializing file " + f.getAbsolutePath()); + callgraph = deserializeStaticCallGraph(new File(args[1])); + } catch (IOException e) { + LOGGER.error("Could not deserialize static call graph", e); + } catch (ClassNotFoundException e) { + LOGGER.error("This shouldn't happen, go fix your CLASSPATH", e); } - private static String asCsv(String name) { - return name.endsWith(CSV_SUFFIX) ? name : (name + CSV_SUFFIX); + // Second argument: the jacoco.xml + JacocoCoverage jacocoCoverage = null; + try { + File f = new File(args[2]); + LOGGER.info("Reading JaCoCo coverage file " + f.getAbsolutePath()); + jacocoCoverage = new JacocoCoverage(f.getAbsolutePath()); + } catch (IOException | ParserConfigurationException | JAXBException | SAXException e) { + LOGGER.error("Could not read JaCoCo coverage file", e); + } + + // third argument: the output file + String output = args[3]; + + if (callgraph == null || jacocoCoverage == null) { + // Something went wrong, bail + return; + } + + // forth argument: Jar path to infer entry point signature + String jarPath = args[4]; + try { + new JarFile(jarPath); + } catch(IOException e){ + LOGGER.error("Could not read inference Jar file", e); + } + + // Sixth argument, optional, return type of expected method + Optional returnType = Optional.empty(); + if(args.length > 6) + returnType = Optional.of(args[6]); + + // Seventh argument, optional, parameter types of expected method + Optional paramterTypes = Optional.empty(); + if(args.length > 7) + paramterTypes = Optional.of(args[7]); + + // Fifth argument, class.method input where class can be written as nested classes to generate exact method signature + String entryPoint = null; + try { + entryPoint = generateEntryPoint(jarPath, args[5], returnType, paramterTypes); +// System.out.println(entryPoint); + } catch(IOException e){ + LOGGER.error("Could not generate method signature", e); } + + // Seventh argument, optional, is the depth + Optional depth = Optional.empty(); +// if (args.length > 6) +// depth = Optional.of(Integer.parseInt(args[7])); + + // This method changes the callgraph object + Pruning.pruneOriginalGraph(callgraph, jacocoCoverage); + + maybeInspectReachability(callgraph, depth, jacocoCoverage, entryPoint, output); + + // maybeWriteGraph(callgraph.graph, args[4]); + } + + private static void maybeWriteGraph(Graph graph, String output) { + Utilities.writeGraph(graph, Utilities.defaultExporter(), JCallGraph.asDot(output)); + } + + private static Graph maybeInspectReachability( + StaticCallgraph callgraph, Optional depth, JacocoCoverage jacocoCoverage, String entryPoint, String outputFile) { + + /* Fetch reachability */ + Graph reachability = + Reachability.compute( + callgraph.graph, entryPoint, depth); + + /* Apply coverage */ + jacocoCoverage.applyCoverage(reachability, callgraph.metadata); + + Pruning.pruneReachabilityGraph(reachability, callgraph.metadata, jacocoCoverage); + + /* Should we write the graph to a file? */ + String outputName = getCompleteOutputName(depth, outputFile); + + /* Store reachability in file? */ + Utilities.writeGraph( + reachability, Utilities.coloredExporter(), JCallGraph.asDot(outputName)); + + try { + writeSerializeReachabilityGraph(reachability, asSer(outputName)); + } catch (IOException e) { + LOGGER.error("Error writing serialized reachability graph."); + LOGGER.error(e.getMessage()); + } + + + /* Analyze reachability coverage? */ + if (jacocoCoverage.hasCoverage()) { + CoverageStatistics.analyze( reachability, Optional.of(asCsv(outputName + DELIMITER + COVERAGE))); + } + + return reachability; + } + + private static String getCompleteOutputName(Optional depth, String outputFile) { + /* Should we write the graph to a file? */ + String outputName = outputFile + DELIMITER + REACHABILITY; + + /* Attach depth to name if present */ + if (depth.isPresent()) { + outputName = outputName + DELIMITER + depth.get(); + } + + return outputName; + } + + private static void maybeInspectAncestry( + StaticCallgraph callgraph, TestArguments arguments, JacocoCoverage jacocoCoverage, Optional entryPoint, OptionaloutputName) { + if (arguments.maybeAncestry().isEmpty() || entryPoint.isEmpty()) { + return; + } + + Graph ancestry = + Ancestry.compute( + callgraph.graph, entryPoint.get(), arguments.maybeAncestry().get()); + jacocoCoverage.applyCoverage(ancestry, callgraph.metadata); + + /* Should we store the ancestry in a file? */ + if (outputName.isPresent()) { + String subgraphOutputName = + outputName.get() + + DELIMITER + + ANCESTRY + + DELIMITER + + arguments.maybeAncestry().get(); + Utilities.writeGraph( + ancestry, Utilities.coloredExporter(), JCallGraph.OUTPUT_DIRECTORY + asDot(subgraphOutputName)); + } + } + + private static String asDot(String name) { + return name.endsWith(DOT_SUFFIX) ? name : (name + DOT_SUFFIX); + } + + private static String asSer(String name) { + return name.endsWith(SER_SUFFIX) ? name : (name + SER_SUFFIX); + } + + private static String asCsv(String name) { + return name.endsWith(CSV_SUFFIX) ? name : (name + CSV_SUFFIX); + } + + // + // serializeStaticCallGraph creates a file that contains the bytecode data of the StaticCallgraph object + // Throws: IOException when the file cannot be written to disk + private static void maybeSerializeStaticCallGraph(StaticCallgraph callgraph, BuildArguments arguments) throws IOException{ + if(arguments.maybeOutput().isPresent()) { + File filename = new File(arguments.maybeOutput().get()); + FileOutputStream file = new FileOutputStream(filename); + ObjectOutputStream out = new ObjectOutputStream(file); + out.writeObject(callgraph); + out.close(); + file.close(); + } + } + + private static void writeSerializeReachabilityGraph(Graph reachability, String pathname) throws IOException { + File filename = new File(pathname); + FileOutputStream file = new FileOutputStream(filename); + ObjectOutputStream out = new ObjectOutputStream(file); + out.writeObject(reachability); + out.close(); + file.close(); + } + + // + // deserializeStaticCallGraph reads bytecode and creates a StaticCallgraph object to be returned + // Throws: IOException when file cannot be read + // Throws: ClassNotFoundException when object cannot be read properly + private static StaticCallgraph deserializeStaticCallGraph(TestArguments arguments) throws IOException, ClassNotFoundException{ + return deserializeStaticCallGraph(new File(arguments.maybeBytecodeFile().get())); + } + + private static StaticCallgraph deserializeStaticCallGraph(File f) throws IOException, ClassNotFoundException{ + try (ObjectInput ois = new ObjectInputStream(new FileInputStream(f))) { + return (StaticCallgraph) ois.readObject(); + } + } + + + private static RepoTool maybeObtainTool(GitArguments arguments) throws FileNotFoundException{ + Optional rt = RepoTool.obtainTool(arguments.maybeGetConfig().get()); + if(rt.isPresent()) + return rt.get(); + throw new FileNotFoundException("folderName path is incorrect! Please provide a valid folder"); + } + + private static RepoTool maybeObtainTool(TestArguments arguments) throws FileNotFoundException{ + return new RepoTool(arguments.maybeGetConfig().get()); + } } diff --git a/src/main/java/gr/gousiosg/javacg/stat/MethodVisitor.java b/src/main/java/gr/gousiosg/javacg/stat/MethodVisitor.java index 9e13d062..304a39f0 100644 --- a/src/main/java/gr/gousiosg/javacg/stat/MethodVisitor.java +++ b/src/main/java/gr/gousiosg/javacg/stat/MethodVisitor.java @@ -39,32 +39,33 @@ import java.lang.reflect.Method; import java.util.*; +import java.util.stream.Collectors; import static gr.gousiosg.javacg.stat.support.IgnoredConstants.IGNORED_METHOD_NAMES; /** - * The simplest of method visitors, prints any invoked method - * signature for all method invocations. - * - * Class copied with modifications from CJKM: http://www.spinellis.gr/sw/ckjm/ + * The simplest of method visitors, prints any invoked method signature for all method invocations. + * + *

Class copied with modifications from CJKM: http://www.spinellis.gr/sw/ckjm/ */ public class MethodVisitor extends EmptyVisitor { private static final Logger LOGGER = LoggerFactory.getLogger(MethodVisitor.class); private static final Boolean EXPAND = true; private static final Boolean DONT_EXPAND = false; - + private final JarMetadata jarMetadata; JavaClass visitedClass; + private boolean isTestMethod; private MethodGen mg; private ConstantPoolGen cp; private String format; - - // methodCalls helps us build the (caller -> receiver) call graph private Set> methodCalls = new HashSet<>(); - private final JarMetadata jarMetadata; + private Map, Map>> expansions = new HashMap<>(); + private int currentLineNumber = -1; - public MethodVisitor(MethodGen m, JavaClass jc, JarMetadata jarMetadata) { + public MethodVisitor(MethodGen m, JavaClass jc, JarMetadata jarMetadata, boolean isTestMethod) { this.jarMetadata = jarMetadata; + this.isTestMethod = isTestMethod; visitedClass = jc; mg = m; cp = mg.getConstantPool(); @@ -82,24 +83,101 @@ private String argumentList(Type[] arguments) { return sb.toString(); } - public Set> start() { - if (mg.isAbstract() || mg.isNative()) - return Collections.emptySet(); + private LinkedList findLeaders(InstructionList il) { + // https://www.geeksforgeeks.org/basic-blocks-in-compiler-design/ + LinkedList is = new LinkedList<>(); + Set leaders = new HashSet<>(); - for (InstructionHandle ih = mg.getInstructionList().getStart(); - ih != null; ih = ih.getNext()) { + leaders.add(il.getStart()); + + for (InstructionHandle ih = il.getStart(); ih != null; ih = ih.getNext()) { + is.addLast(ih); Instruction i = ih.getInstruction(); - - if (!visitInstruction(i)) - i.accept(this); + + if (i instanceof IfInstruction) { + IfInstruction ifi = (IfInstruction) i; + leaders.add(ifi.getTarget()); + leaders.add(ih.getNext()); + } else if (i instanceof GOTO) { + // TODO unconditional GOTOs + } else if (i instanceof Select) { + // TODO switch-case + } else if (i instanceof ReturnInstruction || i instanceof ATHROW) { + if (ih.getNext() != null) + leaders.add(ih.getNext()); + } } + + LinkedList sortedLeaders = new LinkedList<>(leaders); + Collections.sort(sortedLeaders, Comparator.comparingInt(InstructionHandle::getPosition)); + + return sortedLeaders; + } + + private List> computeBasicBlocks(InstructionList il) { + LinkedList leaders = findLeaders(il); + + LinkedList> ret = new LinkedList<>(); + + LinkedList currentBlock = new LinkedList<>(); + + { + // First instruction is always a leader, add manually + leaders.removeFirst(); + currentBlock.addLast(il.getStart()); + } + + for (InstructionHandle ih = il.getStart().getNext(); ih != null; ih = ih.getNext()) { + if (!leaders.isEmpty() && ih == leaders.getFirst()) { + // Found start of next BB + ret.addLast(currentBlock); + currentBlock = new LinkedList<>(); + currentBlock.addLast(ih); + leaders.removeFirst(); + } else { + // Regular instruction, just add to current block + currentBlock.addLast(ih); + } + } + + // Add last BB + ret.addLast(currentBlock); + + return ret; + } + + public Set> start() { + if (mg.isAbstract() || mg.isNative()) return Collections.emptySet(); + + boolean includeExceptionBasicBlocks = Boolean.getBoolean("jcg.includeExceptionBasicBlocks"); + + List> bbs = this.computeBasicBlocks(mg.getInstructionList()); + + for (LinkedList bb : bbs) { + if (!includeExceptionBasicBlocks && bb.getLast().getInstruction() instanceof ATHROW) { + // skip BBs that throw exceptions + continue; + } + + for (InstructionHandle ih : bb) { + Instruction i = ih.getInstruction(); + + if (!visitInstruction(i)) { + int currentBytecodeOffset = ih.getPosition(); + currentLineNumber = mg.getLineNumberTable(cp).getSourceLine(currentBytecodeOffset); + i.accept(this); + } + + } + } + return methodCalls; } private boolean visitInstruction(Instruction i) { short opcode = i.getOpcode(); return ((InstructionConst.getInstruction(opcode) != null) - && !(i instanceof ConstantPushInstruction) + && !(i instanceof ConstantPushInstruction) && !(i instanceof ReturnInstruction)); } @@ -129,77 +207,140 @@ public void visitINVOKEDYNAMIC(INVOKEDYNAMIC i) { } private void visit(InvokeInstruction i, Boolean shouldExpand) { - /* caller method info */ - String callerClassType = visitedClass.getClassName(); - String callerMethodName = mg.getName(); - Type[] callerArgumentTypes = mg.getArgumentTypes(); - Type callerReturnType = mg.getReturnType(); - String callerSignature = MethodSignatureUtil.fullyQualifiedMethodSignature(callerClassType, callerMethodName, callerArgumentTypes, callerReturnType); - - /* receiver method info */ - String receiverClassType = String.format(format, i.getReferenceType(cp)); - String receiverMethodName = i.getMethodName(cp); - Type[] receiverArgumentTypes = i.getArgumentTypes(cp); - Type receiverReturnType = i.getReturnType(cp); - String receiverSignature = MethodSignatureUtil.fullyQualifiedMethodSignature(receiverClassType, receiverMethodName, receiverArgumentTypes, receiverReturnType); - - /* Record initial method call */ - methodCalls.add(createEdge(callerSignature, receiverSignature)); - - if (shouldExpand && !IGNORED_METHOD_NAMES.contains(receiverMethodName)) { - Optional> maybeReceiverType = jarMetadata.getClass(receiverClassType); + Node caller = new Node(mg, visitedClass); + Node receiver = new Node(i, cp, format); + methodCalls.add(createEdge(caller.signature, receiver.signature)); + + if (isTestMethod) { + jarMetadata.testMethods.add(caller.signature); + } + + // save the line number and method call + jarMetadata.impliedMethodCalls.putIfAbsent(receiver.signature, new HashSet<>()); + jarMetadata + .impliedMethodCalls + .get(receiver.signature) + .add(filenameAndLineNumber(visitedClass.getSourceFileName(), currentLineNumber)); + + // decide if we should look at a potential expansion + if (shouldExpand && !IGNORED_METHOD_NAMES.contains(receiver.method)) { + + // get the class types + Optional> maybeReceiverType = jarMetadata.getClass(receiver.clazz); + Optional> maybeCallerType = jarMetadata.getClass(caller.clazz); + if (maybeReceiverType.isEmpty()) { - LOGGER.error("Couldn't find Receiver class: " + receiverClassType); + LOGGER.error("Couldn't find Receiver class: " + receiver.clazz); return; - } - - Optional> maybeCallerType = jarMetadata.getClass(callerClassType); - if (maybeCallerType.isEmpty()) { - LOGGER.error("Couldn't find Caller class: " + callerClassType); + } else if (maybeCallerType.isEmpty()) { + LOGGER.error("Couldn't find Caller class: " + caller.clazz); return; } - Optional maybeCallingMethod = jarMetadata.getInspector() - .getTopLevelSignature( - maybeCallerType.get(), - MethodSignatureUtil.namedMethodSignature(callerMethodName, callerArgumentTypes, callerReturnType) - ); + // find the method that initiated a call to another method + Optional maybeCallingMethod = + jarMetadata + .getInspector() + .getTopLevelSignature( + maybeCallerType.get(), + MethodSignatureUtil.namedMethodSignature( + caller.method, caller.argumentTypes, caller.returnType)); if (maybeCallingMethod.isEmpty()) { - LOGGER.error("Couldn't find top level signature for " + callerSignature); + LOGGER.error("Couldn't find top level signature for " + caller.signature); return; } if (maybeCallingMethod.get().isBridge()) { - jarMetadata.addBridgeMethod(receiverSignature); + // skip the expansion if it's a bridge method + jarMetadata.addBridgeMethod(caller.signature); } else { - /* Expand to all possible receiver class types */ - expand(maybeReceiverType.get(), receiverMethodName, receiverArgumentTypes, receiverReturnType, callerSignature); + // record the virtual method and expand it to subtypes + jarMetadata.addVirtualMethod(receiver.signature); + expand(caller, receiver, maybeReceiverType.get()); } } - } - private void expand(Class receiverType, String receiverMethodName, Type[] receiverArgumentTypes, Type receiverReturnType, String callerSignature) { + private void expand(Node caller, Node receiver, Class receiverType) { + if (Object.class.equals(receiverType)) return; + ClassHierarchyInspector inspector = jarMetadata.getInspector(); - LOGGER.info("\tExpanding to subtypes of " + receiverType.getName()); - jarMetadata.getReflections().getSubTypesOf(receiverType) - .stream() - .map(subtype -> - inspector.getTopLevelSignature( - subtype, - MethodSignatureUtil.namedMethodSignature(receiverMethodName, receiverArgumentTypes, receiverReturnType) - ) - ) - .filter(Optional::isPresent) - .map(Optional::get) - .map(MethodSignatureUtil::fullyQualifiedMethodSignature) - /* Record expanded method call */ - .forEach(toSubtypeSignature -> methodCalls.add(createEdge(callerSignature, toSubtypeSignature))); + expansions.putIfAbsent(receiverType, new HashMap<>()); + Set exps = expansions.get(receiverType).get(receiver.method); + if (exps == null) { + LOGGER.info("\tExpanding to subtypes of " + receiverType.getName()); + exps = + jarMetadata.getReflections().getSubTypesOf(receiverType).stream() + .map( + subtype -> + inspector.getTopLevelSignature( + subtype, + MethodSignatureUtil.namedMethodSignature( + receiver.method, receiver.argumentTypes, receiver.returnType))) + .flatMap(Optional::stream) // Remove empty optionals + .map(MethodSignatureUtil::fullyQualifiedMethodSignature) + .collect(Collectors.toSet()); + + expansions.get(receiverType).put(receiver.method, exps); + } + + /* Record expanded method call */ + exps.forEach( + expansionSignature -> { + methodCalls.add(createEdge(caller.signature, expansionSignature)); + jarMetadata.addConcreteMethod(expansionSignature); + }); } public Pair createEdge(String from, String to) { return new Pair<>(from, to); } + + private void recordLineNumber(Node receiver) { + if (currentLineNumber < 0) { + LOGGER.error(currentLineNumber + " cannot be negative!"); + System.exit(1); + } + + throw new Error("TODO: record { class + line -> receiverSignature } in JarMetadata"); + } + + private String filenameAndLineNumber(String filename, int lineNumber) { + return String.format("%s:%d", filename, lineNumber); + } + + /** + * Contains information relating to a method of a class + * + *

For internal use in {@link MethodVisitor} only, this is NOT a graph vertex. + */ + private static class Node { + String clazz; + String method; + Type[] argumentTypes; + Type returnType; + String signature; + + private Node(MethodGen mg, JavaClass visitedClass) { + this.clazz = visitedClass.getClassName(); + this.method = mg.getName(); + this.argumentTypes = mg.getArgumentTypes(); + this.returnType = mg.getReturnType(); + this.signature = + MethodSignatureUtil.fullyQualifiedMethodSignature( + clazz, method, argumentTypes, returnType); + } + + private Node(InvokeInstruction i, ConstantPoolGen cp, String format) { + this.clazz = String.format(format, i.getReferenceType(cp)); + this.method = i.getMethodName(cp); + this.argumentTypes = i.getArgumentTypes(cp); + this.returnType = i.getReturnType(cp); + this.signature = + MethodSignatureUtil.fullyQualifiedMethodSignature( + clazz, method, argumentTypes, returnType); + } + } } diff --git a/src/main/java/gr/gousiosg/javacg/stat/support/coverage/ColoredNode.java b/src/main/java/gr/gousiosg/javacg/stat/coverage/ColoredNode.java similarity index 68% rename from src/main/java/gr/gousiosg/javacg/stat/support/coverage/ColoredNode.java rename to src/main/java/gr/gousiosg/javacg/stat/coverage/ColoredNode.java index 53e2b10c..8da549c1 100644 --- a/src/main/java/gr/gousiosg/javacg/stat/support/coverage/ColoredNode.java +++ b/src/main/java/gr/gousiosg/javacg/stat/coverage/ColoredNode.java @@ -1,8 +1,11 @@ -package gr.gousiosg.javacg.stat.support.coverage; +package gr.gousiosg.javacg.stat.coverage; -public class ColoredNode { +import java.io.Serializable; + +public class ColoredNode implements Serializable { /* Colors */ + private static final String IMPLIED_COVERAGE_COLOR = "skyblue"; private static final String LIGHT_GREEN = "greenyellow"; private static final String MEDIUM_GREEN = "green1"; private static final String MEDIUM_DARK_GREEN = "green3"; @@ -10,9 +13,11 @@ public class ColoredNode { private static final String FIREBRICK = "lightpink"; private static final String ENTRYPOINT_COLOR = "lightgoldenrod"; private static final String NO_COLOR = "ghostwhite"; + private static final String TEST_NODE_COLOR = "plum"; private final String label; private String color = NO_COLOR; + private boolean excluded = false; private boolean covered = false; private int linesCovered = 0; private int linesMissed = 0; @@ -27,7 +32,13 @@ public String getColor() { return color; } - public String getLabel() { return this.label; } + public void setColor(String color) { + this.color = color; + } + + public String getLabel() { + return this.label; + } public void mark(Report.Package.Class.Method method) { @@ -52,23 +63,33 @@ public void mark(Report.Package.Class.Method method) { } } + chooseColor(); + } + + private void chooseColor() { if (!this.color.equals(ENTRYPOINT_COLOR)) { float lineRatio = lineRatio(); if (lineRatio > 0.75) { this.color = DARK_GREEN; - } else if (lineRatio > 0.5) { + } else if (lineRatio > 0.5) { this.color = MEDIUM_DARK_GREEN; } else if (lineRatio > 0.25) { this.color = MEDIUM_GREEN; - } else { + } else if (lineRatio > 0.02) { this.color = LIGHT_GREEN; + } else { + this.color = FIREBRICK; } } } + public void markImpliedCoverage() { + this.covered = true; + this.color = IMPLIED_COVERAGE_COLOR; + } + public void markMissing() { - if (!covered && !(this.color.equals(ENTRYPOINT_COLOR))) - this.color = FIREBRICK; + if (!covered && !(this.color.equals(ENTRYPOINT_COLOR))) this.color = FIREBRICK; } public boolean covered() { @@ -87,6 +108,19 @@ public boolean equals(Object obj) { return node.label.equals(this.label); } + public boolean isExcluded() { + return excluded; + } + + public void setExcluded(boolean excluded) { + this.excluded = excluded; + if (excluded) { + this.color = TEST_NODE_COLOR; + } else { + chooseColor(); + } + } + public int getLinesCovered() { return linesCovered; } @@ -103,7 +137,11 @@ public int getBranchesMissed() { return branchesMissed; } - private float lineRatio() { - return (float) linesCovered / (linesCovered + linesMissed); + public float lineRatio() { + return (float) linesCovered / ((float) linesCovered + (float) linesMissed); + } + + public String toString() { + return getLabel(); } } diff --git a/src/main/java/gr/gousiosg/javacg/stat/support/coverage/CoverageStatistics.java b/src/main/java/gr/gousiosg/javacg/stat/coverage/CoverageStatistics.java similarity index 60% rename from src/main/java/gr/gousiosg/javacg/stat/support/coverage/CoverageStatistics.java rename to src/main/java/gr/gousiosg/javacg/stat/coverage/CoverageStatistics.java index 08bbd500..89c282cd 100644 --- a/src/main/java/gr/gousiosg/javacg/stat/support/coverage/CoverageStatistics.java +++ b/src/main/java/gr/gousiosg/javacg/stat/coverage/CoverageStatistics.java @@ -1,4 +1,4 @@ -package gr.gousiosg.javacg.stat.support.coverage; +package gr.gousiosg.javacg.stat.coverage; import gr.gousiosg.javacg.stat.JCallGraph; import org.jgrapht.Graph; @@ -16,46 +16,43 @@ public class CoverageStatistics { private static final Logger LOGGER = LoggerFactory.getLogger(CoverageStatistics.class); private static final String SEPARATOR = "#############################"; - @Writeable private final long edgeCount; - @Writeable private final long nodesCovered; - @Writeable private final long nodeCount; - @Writeable private final int linesCovered; - @Writeable private final int linesMissed; - @Writeable private final int branchesCovered; - @Writeable private final int branchesMissed; + @Writeable + private long edgeCount = 0; + @Writeable + private long nodesCovered = 0; + @Writeable + private long nodeCount = 0; + @Writeable + private int linesCovered = 0; + @Writeable + private int linesMissed = 0; + @Writeable + private int branchesCovered = 0; + @Writeable + private int branchesMissed = 0; /** * Quantifies the coverage quality of the graph + * * @param graph the graph to quantify coverage quality for */ private CoverageStatistics(Graph graph) { - /* Instantiate temporary values */ - int tempNodesCovered = 0; - int tempLinesCovered = 0; - int tempLinesMissed = 0; - int tempBranchesCovered = 0; - int tempBranchesMissed = 0; - - /* Iterate over graph and gather values */ for (ColoredNode node : graph.vertexSet()) { - tempLinesCovered += node.getLinesCovered(); - tempLinesMissed += node.getLinesMissed(); - tempBranchesCovered += node.getBranchesCovered(); - tempBranchesMissed += node.getBranchesMissed(); - if (node.covered()) { - tempNodesCovered += 1; + if (node.isExcluded()) { + continue; } - } - /* Assign values */ - this.linesCovered = tempLinesCovered; - this.linesMissed = tempLinesMissed; - this.branchesCovered = tempBranchesCovered; - this.branchesMissed = tempBranchesMissed; - this.nodesCovered = tempNodesCovered; - this.nodeCount = graph.vertexSet().size(); - this.edgeCount = graph.edgeSet().size(); + this.linesCovered += node.getLinesCovered(); + this.linesMissed += node.getLinesMissed(); + this.branchesCovered += node.getBranchesCovered(); + this.branchesMissed += node.getBranchesMissed(); + this.edgeCount += graph.outDegreeOf(node); + this.nodeCount++; + if (node.covered()) { + this.nodesCovered += 1; + } + } } public static void analyze(Graph graph, Optional outputName) { @@ -70,10 +67,28 @@ public static void analyze(Graph graph, Optional field.isAnnotationPresent(Writeable.class)) + .forEach( + f -> { + try { + writer.write(f.getName() + "," + f.get(statistics) + "\n"); + } catch (IllegalAccessException | IOException e) { + e.printStackTrace(); + LOGGER.error("Unable to write statistics to " + fileName); + } + }); + writer.close(); + } + private void announce() { float nodeCoverage = ((float) this.nodesCovered) / this.nodeCount * 100; float lineCoverage = ((float) this.linesCovered) / (this.linesCovered + linesMissed) * 100; - float branchCoverage = ((float) this.branchesCovered) / (this.branchesCovered + this.branchesMissed) * 100; + float branchCoverage = + ((float) this.branchesCovered) / (this.branchesCovered + this.branchesMissed) * 100; nodeCoverage = (Float.isNaN(nodeCoverage)) ? 0 : nodeCoverage; lineCoverage = (Float.isNaN(lineCoverage)) ? 0 : lineCoverage; @@ -93,20 +108,4 @@ private void announce() { LOGGER.info("Branch Coverage: " + String.format("%.2f", branchCoverage) + "%"); LOGGER.info(SEPARATOR); } - - private static void toCsv(CoverageStatistics statistics, String fileName) throws Exception { - if (statistics == null) return; - FileWriter writer = new FileWriter(JCallGraph.OUTPUT_DIRECTORY + fileName); - Arrays.stream(CoverageStatistics.class.getDeclaredFields()) - .filter(field -> field.isAnnotationPresent(Writeable.class)) - .forEach(f -> { - try { - writer.write(f.getName() + "," + f.get(statistics) + "\n"); - } catch (IllegalAccessException | IOException e) { - e.printStackTrace(); - LOGGER.error("Unable to write statistics to " + fileName); - } - }); - writer.close(); - } -} \ No newline at end of file +} diff --git a/src/main/java/gr/gousiosg/javacg/stat/coverage/JacocoCoverage.java b/src/main/java/gr/gousiosg/javacg/stat/coverage/JacocoCoverage.java new file mode 100644 index 00000000..80814610 --- /dev/null +++ b/src/main/java/gr/gousiosg/javacg/stat/coverage/JacocoCoverage.java @@ -0,0 +1,171 @@ +package gr.gousiosg.javacg.stat.coverage; + +import gr.gousiosg.javacg.stat.support.JarMetadata; +import gr.gousiosg.javacg.stat.support.MethodSignatureUtil; +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultEdge; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.xml.sax.SAXException; + +import javax.xml.bind.JAXBElement; +import javax.xml.bind.JAXBException; +import javax.xml.parsers.ParserConfigurationException; +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; + +import static gr.gousiosg.javacg.stat.graph.Utilities.nodeMap; + +public class JacocoCoverage { + + public static final String METHOD_TYPE = "METHOD"; + public static final String LINE_TYPE = "LINE"; + public static final String BRANCH_TYPE = "BRANCH"; + private static final Logger LOGGER = LoggerFactory.getLogger(JacocoCoverage.class); + @SuppressWarnings("UnusedAssignment") + private boolean hasCoverage = false; + + private final Map methodCoverage = new HashMap<>(); + private final Set coveredLines = new HashSet<>(); + + /** + * Create a {@link JacocoCoverage} object + * + * @param path the JaCoCo coverage XML file to parse + */ + public JacocoCoverage(String path) + throws IOException, ParserConfigurationException, JAXBException, SAXException { + + /* Convert the jacoco.xml file into a Report object */ + Report report = JacocoCoverageParser.getReport(path); + + /* Iterate over all packages in report */ + for (Report.Package pkg : report.getPackage()) { + + /* Iterate over all classes in a package */ + pkg.getClazz() + .forEach( + clazz -> { + /* Find all methods in a class */ + List methods = + clazz.getContent().stream() + .filter(s -> s instanceof JAXBElement) + .map(s -> (JAXBElement) s) + .filter(je -> je.getValue() instanceof Report.Package.Class.Method) + .map(je -> (Report.Package.Class.Method) je.getValue()) + .collect(Collectors.toList()); + + /* Store "covered" methods in methodCoverage */ + methods.forEach( + method -> { + String qualifiedName = + MethodSignatureUtil.fullyQualifiedMethodSignature( + clazz.getName(), method.getName(), method.getDesc()); + methodCoverage.putIfAbsent(qualifiedName, method); + }); + }); + + /* Iterate over all source files in a package */ + pkg.getSourcefile() + .forEach( + rawSrcFile -> + rawSrcFile.getContent().stream() + .filter(s -> s instanceof JAXBElement) + .map(s -> (JAXBElement) s) + .filter(je -> je.getValue() instanceof Report.Package.Sourcefile.Line) + .map(je -> (Report.Package.Sourcefile.Line) je.getValue()) + .filter( + line -> + Byte.toUnsignedInt(line.cb) > 0 || Byte.toUnsignedInt(line.ci) > 0) + .forEach( + line -> + coveredLines.add( + String.format( + "%s:%d", + rawSrcFile.getName(), Short.toUnsignedInt(line.nr))))); + } + + /* Indicate that coverage has been applied */ + hasCoverage = true; + } + + public void applyCoverage(Graph graph, JarMetadata metadata) { + LOGGER.info("Applying coverage!"); + Map nodeMap = nodeMap(graph.vertexSet()); + nodeMap + .keySet() + .forEach( + method -> { + if (metadata.testMethods.contains(method)) { + nodeMap.get(method).setExcluded(true); + return; + } + + if (methodCoverage.containsKey(method)) { + Report.Package.Class.Method m = methodCoverage.get(method); + nodeMap.get(method).mark(m); + } else { + // didn't find it in methodCoverage? let's see if there is implied coverage... + + Set impliedCalls = metadata.impliedMethodCalls.get(method); + if (impliedCalls == null) { + LOGGER.warn("Couldn't find coverage for " + method); + nodeMap.get(method).markMissing(); + return; + } + + boolean impliedCoverage = + impliedCalls.stream() + .anyMatch(fileAndLine -> coveredLines.contains(fileAndLine)); + + if (impliedCoverage) { + nodeMap.get(method).markImpliedCoverage(); + } else { + LOGGER.warn("Couldn't find coverage for " + method); + nodeMap.get(method).markMissing(); + } + } + }); + } + + public boolean hasCoverage() { + return hasCoverage; + } + + public boolean containsMethod(String methodSignature) { + return methodCoverage.containsKey(methodSignature); + } + + public boolean hasNonzeroCoverage(String methodSignature) { + if (!containsMethod(methodSignature)) { + return false; + } + + Report.Package.Class.Method method = methodCoverage.get(methodSignature); + + for (Report.Package.Class.Method.Counter counter : method.getCounter()) { + switch (counter.getType()) { + case JacocoCoverage.METHOD_TYPE: + case JacocoCoverage.LINE_TYPE: + case JacocoCoverage.BRANCH_TYPE: { + if (hasNonzeroCoverage(counter)) { + return true; + } + } + default: + } + } + + return false; + } + + private boolean hasNonzeroCoverage(Report.Package.Class.Method.Counter counter) { + return counter.getCovered() > 0; + } + + public Map getMethodCoverage() { + //methodCoverage.get(a). + return methodCoverage; + } +} diff --git a/src/main/java/gr/gousiosg/javacg/stat/support/coverage/JacocoCoverageParser.java b/src/main/java/gr/gousiosg/javacg/stat/coverage/JacocoCoverageParser.java similarity index 73% rename from src/main/java/gr/gousiosg/javacg/stat/support/coverage/JacocoCoverageParser.java rename to src/main/java/gr/gousiosg/javacg/stat/coverage/JacocoCoverageParser.java index c609327a..f4b8692e 100644 --- a/src/main/java/gr/gousiosg/javacg/stat/support/coverage/JacocoCoverageParser.java +++ b/src/main/java/gr/gousiosg/javacg/stat/coverage/JacocoCoverageParser.java @@ -1,4 +1,4 @@ -package gr.gousiosg.javacg.stat.support.coverage; +package gr.gousiosg.javacg.stat.coverage; import org.xml.sax.InputSource; import org.xml.sax.SAXException; @@ -14,18 +14,20 @@ import java.io.IOException; public class JacocoCoverageParser { - private static final String XML_LOAD_EXTERNAL_DTD = "http://apache.org/xml/features/nonvalidating/load-external-dtd"; + private static final String XML_LOAD_EXTERNAL_DTD = + "http://apache.org/xml/features/nonvalidating/load-external-dtd"; private static final String SAX_VALIDATION = "http://xml.org/sax/features/validation"; /** * Parse a JaCoCO XML file * * @param filepath the path to the xml file - * @return A {@link gr.gousiosg.javacg.stat.support.coverage.Report} - * (These classes are automatically generated and placed in the folder: + * @return A {@link gr.gousiosg.javacg.stat.coverage.Report} (These classes are automatically + * generated and placed in the folder: * target/classes/gr/gousiosg/javacg/stat/support/coverage) */ - public static Report getReport(String filepath) throws JAXBException, IOException, SAXException, ParserConfigurationException { + public static Report getReport(String filepath) + throws JAXBException, IOException, SAXException, ParserConfigurationException { JAXBContext jc = JAXBContext.newInstance(Report.class); SAXParserFactory spf = SAXParserFactory.newInstance(); diff --git a/src/main/java/gr/gousiosg/javacg/stat/support/coverage/Writeable.java b/src/main/java/gr/gousiosg/javacg/stat/coverage/Writeable.java similarity index 83% rename from src/main/java/gr/gousiosg/javacg/stat/support/coverage/Writeable.java rename to src/main/java/gr/gousiosg/javacg/stat/coverage/Writeable.java index fb701db7..a83be08f 100644 --- a/src/main/java/gr/gousiosg/javacg/stat/support/coverage/Writeable.java +++ b/src/main/java/gr/gousiosg/javacg/stat/coverage/Writeable.java @@ -1,4 +1,4 @@ -package gr.gousiosg.javacg.stat.support.coverage; +package gr.gousiosg.javacg.stat.coverage; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/src/main/java/gr/gousiosg/javacg/stat/graph/Ancestry.java b/src/main/java/gr/gousiosg/javacg/stat/graph/Ancestry.java new file mode 100644 index 00000000..c0ab0f71 --- /dev/null +++ b/src/main/java/gr/gousiosg/javacg/stat/graph/Ancestry.java @@ -0,0 +1,108 @@ +package gr.gousiosg.javacg.stat.graph; + +import gr.gousiosg.javacg.stat.coverage.ColoredNode; +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultDirectedGraph; +import org.jgrapht.graph.DefaultEdge; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; + +public class Ancestry { + + private static final Logger LOGGER = LoggerFactory.getLogger(Ancestry.class); + + /** + * Computes the ancestry of a given entrypoint. This notion is similar to reachability in that we + * perform a breadth-first search of all *parent* nodes starting from the entrypoint. + * + * @param graph the graph to inspect + * @param entrypoint the starting point in the graph + * @param ancestryDepth how many levels breadth-first search should inspect + * @return the ancestry {@link Graph} + */ + public static Graph compute( + Graph graph, String entrypoint, int ancestryDepth) { + + if (!graph.containsVertex(entrypoint)) { + LOGGER.error("---> " + entrypoint + "<---"); + LOGGER.error("The graph doesn't contain the vertex specified as the entry point!"); + throw new InputMismatchException("graph doesn't contain vertex " + entrypoint); + } + + LOGGER.info("Starting ancestry at entry point: " + entrypoint); + LOGGER.info("Traversing to depth " + ancestryDepth); + + /* Book-keeping */ + Graph ancestry = new DefaultDirectedGraph<>(DefaultEdge.class); + Map nodeMap = new HashMap<>(); + Deque parentsToInspect = new ArrayDeque<>(); + Set seenBefore = new HashSet<>(); + Set nextLevel = new HashSet<>(); + + /* Add root node to ancestry graph */ + ColoredNode root = new ColoredNode(entrypoint); + ancestry.addVertex(root); + nodeMap.put(entrypoint, root); + parentsToInspect.push(entrypoint); + + int currentDepth = 0; + while (!parentsToInspect.isEmpty()) { + + if (ancestryDepth < currentDepth) { + break; + } + + /* + * Loop over all nodes that we haven't seen yet and are reachable at depth + * "currentDepth" + */ + while (!parentsToInspect.isEmpty()) { + + /* Fetch the next node */ + String child = parentsToInspect.pop(); + ColoredNode childNode = + nodeMap.containsKey(child) ? nodeMap.get(child) : new ColoredNode(child); + + /* Keep track of the nodes that we've seen before */ + seenBefore.add(child); + if (!nodeMap.containsKey(child)) { + ancestry.addVertex(childNode); + nodeMap.put(child, childNode); + } + + graph + .incomingEdgesOf(child) + .forEach( + incomingEdge -> { + String parent = graph.getEdgeSource(incomingEdge); + ColoredNode parentNode = + nodeMap.containsKey(parent) ? nodeMap.get(parent) : new ColoredNode(parent); + + if (!nodeMap.containsKey(parent)) { + nodeMap.put(parent, parentNode); + ancestry.addVertex(parentNode); + } + + ancestry.addEdge(parentNode, childNode); + + /* Have we visited this vertex before? */ + if (!seenBefore.contains(parent)) { + nextLevel.add(parent); + seenBefore.add(parent); + } + }); + } + + currentDepth++; + + /* we will inspect all of these nodes in the next iteration of the search */ + parentsToInspect.addAll(nextLevel); + nextLevel.clear(); + } + + nodeMap.get(entrypoint).markEntryPoint(); + return ancestry; + } +} diff --git a/src/main/java/gr/gousiosg/javacg/stat/graph/Constants.java b/src/main/java/gr/gousiosg/javacg/stat/graph/Constants.java new file mode 100644 index 00000000..527190b8 --- /dev/null +++ b/src/main/java/gr/gousiosg/javacg/stat/graph/Constants.java @@ -0,0 +1,14 @@ +package gr.gousiosg.javacg.stat.graph; + +public class Constants { + protected static final String DOT_NODE_DELIMITER = "\""; + protected static final String RANK_DIRECTION = "rankdir"; + protected static final String LEFT_TO_RIGHT = "LR"; + protected static final String RANK_VERTICAL_SEPARATION = "ranksep"; + protected static final Double VERTICAL_SEPARATION_VALUE = 1.5; + protected static final String LABEL = "label"; + protected static final String STYLE = "style"; + protected static final String FILLCOLOR = "fillcolor"; + protected static final String FILLED = "filled"; + protected static final String LINERATIO = "lineratio"; +} diff --git a/src/main/java/gr/gousiosg/javacg/stat/graph/Pruning.java b/src/main/java/gr/gousiosg/javacg/stat/graph/Pruning.java new file mode 100644 index 00000000..6286ae50 --- /dev/null +++ b/src/main/java/gr/gousiosg/javacg/stat/graph/Pruning.java @@ -0,0 +1,210 @@ +package gr.gousiosg.javacg.stat.graph; + +import gr.gousiosg.javacg.stat.coverage.ColoredNode; +import gr.gousiosg.javacg.stat.coverage.JacocoCoverage; +import gr.gousiosg.javacg.stat.support.JarMetadata; +import gr.gousiosg.javacg.stat.support.TestArguments; +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultEdge; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import static gr.gousiosg.javacg.stat.graph.Utilities.nodeMap; + +public class Pruning { + private static final Logger LOGGER = LoggerFactory.getLogger(Pruning.class); + + /** + * Wrapper method to call {@link Pruning} methods + * + * @param callgraph the graph + * @param coverage the coverage + */ + public static void pruneOriginalGraph(StaticCallgraph callgraph, JacocoCoverage coverage) { + markConcreteBridgeTargets(callgraph.graph, callgraph.metadata); + pruneBridgeMethods(callgraph.graph, callgraph.metadata); + pruneConcreteMethods(callgraph.graph, callgraph.metadata, coverage); + pruneMethodsFromTests(callgraph.graph, callgraph.metadata, coverage); + } + + public static void pruneReachabilityGraph(Graph reachability, JarMetadata metadata, JacocoCoverage coverage) { + pruneMethodsFromTestsThatAreReachable(reachability, metadata, coverage); + } + + /** + * Remove all bridge / synthetic methods that were created during type erasure See + * https://docs.oracle.com/javase/tutorial/java/generics/bridgeMethods.html for more information. + * + * @param graph the graph + * @param metadata the metadata of the graph + */ + private static void pruneBridgeMethods(Graph graph, JarMetadata metadata) { + metadata + .getBridgeMethods() + .forEach( + bridgeNode -> { + /* Fetch the bridge method and make sure it has exactly one outgoing edge */ + Optional maybeEdge = + graph.outgoingEdgesOf(bridgeNode).stream().findFirst(); + + if (graph.outDegreeOf(bridgeNode) != 1 || maybeEdge.isEmpty()) { + /* announce the violator */ + LOGGER.error( + "Found a bridge method that doesn't have exactly 1 outgoing edge: " + + bridgeNode + + " : " + + graph.outDegreeOf(bridgeNode)); + /* announce the violator's connections */ + graph + .outgoingEdgesOf(bridgeNode) + .forEach( + e -> { + LOGGER.error( + "\t" + graph.getEdgeSource(e) + " -> " + graph.getEdgeTarget(e)); + }); + System.exit(1); + } + + /* Fetch the bridge method's target */ + String bridgeTarget = graph.getEdgeTarget(maybeEdge.get()); + + /* Redirect all edges from the bridge method to its target */ + graph + .incomingEdgesOf(bridgeNode) + .forEach( + edge -> { + String sourceNode = graph.getEdgeSource(edge); + graph.addEdge(sourceNode, bridgeTarget); + }); + + /* Remove the bridge method from the graph */ + graph.removeVertex(bridgeNode); + }); + } + + /** + * Remove all unused concrete method calls that are present in the graph + * + *

This technique helps us reduce the over-approximation incurred by method expansion. + * + * @param graph the graph + * @param metadata the metadata of the graph + */ + private static void pruneConcreteMethods( + Graph graph, JarMetadata metadata, JacocoCoverage coverage) { + metadata.getConcreteMethods().stream() + .filter(concreteMethod -> !coverage.hasNonzeroCoverage(concreteMethod)) + .forEach(graph::removeVertex); + } + + /** + * Mark the target node of every concrete bridge method as concrete + * + *

If a bridge is concrete, then the bridge target should also be concrete + * + * @param graph the graph + * @param metadata the metadata of the graph + */ + private static void markConcreteBridgeTargets( + Graph graph, JarMetadata metadata) { + metadata.getBridgeMethods().stream() + .filter(metadata::containsConcreteMethod) + .map(graph::outgoingEdgesOf) + .flatMap(Set::stream) + .map(graph::getEdgeTarget) + .forEach(metadata::addConcreteMethod); + } + + /** + * Prunes methods that are only called by tests + *

+ * For example, a test method may call assertEquals. We should remove assertEquals from the graph. + * + * @param graph the graph + * @param metadata the metadata of the graph + */ + private static void pruneMethodsFromTests(Graph graph, JarMetadata metadata, JacocoCoverage coverage) { + var testTargetNodes = metadata.testMethods.stream() + .filter(graph::containsVertex) + .map(graph::outgoingEdgesOf) + .flatMap(Collection::stream) + .map(graph::getEdgeTarget) + .collect(Collectors.toSet()); + + var targetsToRemove = testTargetNodes.stream() + .filter(graph::containsVertex) + .filter(target -> { + if (coverage.containsMethod(target)) { + return false; + } + + if (metadata.testMethods.contains(target)) { + return false; + } + + for (var e : graph.incomingEdgesOf(target)) { + if (!metadata.testMethods.contains(graph.getEdgeSource(e))) { + return false; + } + } + + return true; + }) + .filter(target -> !metadata.testMethods.contains(target)) + .collect(Collectors.toSet()); + + targetsToRemove.forEach(graph::removeVertex); + } + + /** + * Prunes methods that are only called by tests that are in the reachability graph + * * @param graph the graph + * + * @param metadata the metadata of the graph + */ + private static void pruneMethodsFromTestsThatAreReachable(Graph graph, JarMetadata metadata, JacocoCoverage coverage) { + Map nodeMap = nodeMap(graph.vertexSet()); + + var testTargetNodes = metadata.testMethods.stream() + .filter(nodeMap::containsKey) + .map(target -> graph.outgoingEdgesOf(nodeMap.get(target))) + .flatMap(Collection::stream) + .map(graph::getEdgeTarget) + .collect(Collectors.toSet()); + + var targetsToRemove = testTargetNodes.stream() + .filter(graph::containsVertex) + .filter(targetNode -> { + + if (targetNode.covered()) { + return false; + } + + if (coverage.containsMethod(targetNode.getLabel())) { + return false; + } + + if (metadata.testMethods.contains(targetNode.getLabel())) { + return false; + } + + for (var e : graph.incomingEdgesOf(targetNode)) { + if (!metadata.testMethods.contains(graph.getEdgeSource(e).getLabel())) { + return false; + } + } + + return true; + }) + .filter(targetNode -> !metadata.testMethods.contains(targetNode.getLabel())) + .collect(Collectors.toSet()); + + targetsToRemove.forEach(graph::removeVertex); + } +} diff --git a/src/main/java/gr/gousiosg/javacg/stat/graph/Reachability.java b/src/main/java/gr/gousiosg/javacg/stat/graph/Reachability.java new file mode 100644 index 00000000..91d42bc6 --- /dev/null +++ b/src/main/java/gr/gousiosg/javacg/stat/graph/Reachability.java @@ -0,0 +1,114 @@ +package gr.gousiosg.javacg.stat.graph; + +import gr.gousiosg.javacg.stat.coverage.ColoredNode; +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultDirectedGraph; +import org.jgrapht.graph.DefaultEdge; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; + +public class Reachability { + + private static final Logger LOGGER = LoggerFactory.getLogger(Reachability.class); + + /** + * Computes the reachability subgraph from a parent graph, entrypoint, and an optional depth to + * search + * + * @param graph the parent {@link Graph} + * @param entrypoint the root node of the reachability subgraph + * @param maybeMaximumDepth the depth to traverse (e.g., all nodes reachable within N steps from + * the root) + * @return a subgraph containing all reachable nodes as described + */ + public static Graph compute( + Graph graph, String entrypoint, Optional maybeMaximumDepth) { + + if (!graph.containsVertex(entrypoint)) { + LOGGER.error("---> " + entrypoint + "<---"); + LOGGER.error("The graph doesn't contain the vertex specified as the entry point!"); + throw new InputMismatchException("graph doesn't contain vertex " + entrypoint); + } + + if (maybeMaximumDepth.isPresent() && (maybeMaximumDepth.get() < 0)) { + LOGGER.error("Depth " + maybeMaximumDepth.get() + " must be greater than 0!"); + System.exit(1); + } + + LOGGER.info("Starting reachability at entry point: " + entrypoint); + maybeMaximumDepth.ifPresent(d -> LOGGER.info("Traversing to depth " + d)); + + Graph subgraph = new DefaultDirectedGraph<>(DefaultEdge.class); + int currentDepth = 0; + + Deque reachable = new ArrayDeque<>(); + reachable.push(entrypoint); + + Map subgraphNodes = new HashMap<>(); + Set seenBefore = new HashSet<>(); + Set nextLevel = new HashSet<>(); + + while (!reachable.isEmpty()) { + + /* Stop once we've surpassed maximum depth */ + if (maybeMaximumDepth.isPresent() && (maybeMaximumDepth.get() < currentDepth)) { + break; + } + + while (!reachable.isEmpty()) { + /* Visit reachable node */ + String source = reachable.pop(); + ColoredNode sourceNode = + subgraphNodes.containsKey(source) ? subgraphNodes.get(source) : new ColoredNode(source); + + /* Keep track of who we've visited */ + seenBefore.add(source); + if (!subgraphNodes.containsKey(source)) { + subgraph.addVertex(sourceNode); + subgraphNodes.put(source, sourceNode); + } + + /* Check if we can add deeper edges or not */ + if (maybeMaximumDepth.isPresent() && (maybeMaximumDepth.get() == currentDepth)) { + break; + } + + graph + .edgesOf(source) + .forEach( + edge -> { + String target = graph.getEdgeTarget(edge); + ColoredNode targetNode = + subgraphNodes.containsKey(target) + ? subgraphNodes.get(target) + : new ColoredNode(target); + + if (!subgraphNodes.containsKey(target)) { + subgraphNodes.put(target, targetNode); + subgraph.addVertex(targetNode); + } + + if (graph.containsEdge(source, target) + && !subgraph.containsEdge(sourceNode, targetNode)) { + subgraph.addEdge(sourceNode, targetNode); + } + + /* Have we visited this vertex before? */ + if (!seenBefore.contains(target)) { + nextLevel.add(target); + seenBefore.add(target); + } + }); + } + + currentDepth++; + reachable.addAll(nextLevel); + nextLevel.clear(); + } + + subgraphNodes.get(entrypoint).markEntryPoint(); + return subgraph; + } +} diff --git a/src/main/java/gr/gousiosg/javacg/stat/graph/SerializableDefaultDirectedGraph.java b/src/main/java/gr/gousiosg/javacg/stat/graph/SerializableDefaultDirectedGraph.java new file mode 100644 index 00000000..3b716893 --- /dev/null +++ b/src/main/java/gr/gousiosg/javacg/stat/graph/SerializableDefaultDirectedGraph.java @@ -0,0 +1,23 @@ +package gr.gousiosg.javacg.stat.graph; + +import org.jgrapht.graph.DefaultDirectedGraph; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; + + +public class SerializableDefaultDirectedGraph extends DefaultDirectedGraph implements Serializable { + public SerializableDefaultDirectedGraph(Class edgeClass) { + super(edgeClass); + } + + private void writeObject(ObjectOutputStream oos) throws IOException { + oos.defaultWriteObject(); + } + + private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { + ois.defaultReadObject(); + } +} diff --git a/src/main/java/gr/gousiosg/javacg/stat/graph/StaticCallgraph.java b/src/main/java/gr/gousiosg/javacg/stat/graph/StaticCallgraph.java new file mode 100644 index 00000000..1cbf56eb --- /dev/null +++ b/src/main/java/gr/gousiosg/javacg/stat/graph/StaticCallgraph.java @@ -0,0 +1,191 @@ +package gr.gousiosg.javacg.stat.graph; + +import gr.gousiosg.javacg.dyn.Pair; +import gr.gousiosg.javacg.stat.ClassVisitor; +import gr.gousiosg.javacg.stat.support.BuildArguments; +import gr.gousiosg.javacg.stat.support.JarMetadata; +import org.apache.bcel.classfile.ClassParser; +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultEdge; +import org.reflections.Reflections; +import org.reflections.scanners.SubTypesScanner; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.io.UncheckedIOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.*; +import java.util.function.Function; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.stream.Stream; + +import static gr.gousiosg.javacg.stat.graph.Utilities.*; + +public class StaticCallgraph implements Serializable { + + private static transient final Logger LOGGER = LoggerFactory.getLogger(StaticCallgraph.class); + + public JarMetadata metadata; + public SerializableDefaultDirectedGraph graph; + public String JarEntry; + + private StaticCallgraph(Graph graph, JarMetadata jarMetadata) { + this.graph = (SerializableDefaultDirectedGraph) graph; + this.metadata = jarMetadata; + } + + /** + * Builds a static callgraph from the provided jars + * + * @param buildArguments the arguments to build the graph with + * @return a {@link Graph} representing the static callgraph of the combined jars + * @throws InputMismatchException + */ + public static StaticCallgraph build(BuildArguments buildArguments) throws InputMismatchException { + LOGGER.info("Beginning callgraph analysis..."); + var jars = buildArguments.getJars(); + var maybeTestJar = buildArguments.getMaybeTestJar(); + + // 1. SETTING UP FOR GRAPH INSPECTION + /* Load JAR URLs */ + List urls = new ArrayList<>(); + try { + for (Pair pair : jars) { + URL url = new URL("jar:file:" + pair.first + "!/"); + urls.add(url); + } + } catch (MalformedURLException e) { + LOGGER.error("Error loading URLs: " + e.getMessage()); + throw new InputMismatchException("Couldn't load provided JARs"); + } + + if (urls.isEmpty()) { + LOGGER.error("No URLs to scan!"); + throw new InputMismatchException("There are no URLs to scan!"); + } + + /* Setup infrastructure for analysis */ + URLClassLoader cl = + URLClassLoader.newInstance(urls.toArray(new URL[0]), Utilities.class.getClassLoader()); + Reflections reflections = new Reflections(cl, new SubTypesScanner(false)); + JarMetadata jarMetadata = new JarMetadata(cl, reflections); + + /* Store method calls (caller -> receiver) */ + Map> calls = new HashMap<>(); + + // 2. GRAPH INSPECTION + /* iterate over all provided jars */ + for (Pair pair : jars) { + String jarPath = pair.first; + File file = pair.second; + + try (JarFile jarFile = new JarFile(file)) { + boolean isTestJar = (maybeTestJar.isPresent() && jarPath.equals(maybeTestJar.get().first)); + + LOGGER.info("Analyzing: " + jarFile.getName()); + Stream entries = enumerationAsStream(jarFile.entries()); + + Function getClassVisitor = + (ClassParser cp) -> { + try { + return new ClassVisitor(cp.parse(), jarMetadata, isTestJar); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }; + + /* Analyze each jar entry to find callgraph */ + inspectJarEntries(entries, jarPath, getClassVisitor, calls); + + } catch (IOException e) { + LOGGER.error("Error when analyzing JAR \"" + jarPath + "\": + e.getMessage()"); + e.printStackTrace(); + } + } + + /* Convert calls into a graph */ + Graph graph = buildGraph(calls); + return new StaticCallgraph(graph, jarMetadata); + } + + /** + * Takes all (source, destination) method call pairs and stitches them together to create a graph + * + * @param methodCalls the method calls + * @return a {@link Graph} + * @throws InputMismatchException + */ + private static Graph buildGraph(Map> methodCalls) + throws InputMismatchException { + if (methodCalls.keySet().isEmpty()) { + throw new InputMismatchException("There is no call graph to look at!"); + } + + Graph graph = new SerializableDefaultDirectedGraph<>(DefaultEdge.class); + methodCalls + .keySet() + .forEach( + source -> { + /* create source vertex */ + putIfAbsent(graph, source); + + methodCalls + .get(source) + .forEach( + destination -> { + /* create destination vertex */ + putIfAbsent(graph, destination); + + /* connect every (source, destination) pair with an edge */ + graph.addEdge(source, destination); + }); + }); + + return graph; + } + + /** + * Finds all (source, destination) method call pairs within a jar + * + * @param entries the entries of the jar + * @param jarPath the path to the jar + * @param getClassVisitor a {@link ClassVisitor} + * @param calls the data structure containing all (source, destination) method call pairs + */ + private static void inspectJarEntries( + Stream entries, + String jarPath, + Function getClassVisitor, + Map> calls) { + + /* Analyze each jar entry to find callgraph */ + entries + .flatMap( + e -> { + /* Only inspect directories and `*.class` files */ + if (e.isDirectory() || !e.getName().endsWith(".class")) return Stream.of(); + + /* Ignore specified JARs */ + if (shouldIgnoreEntry(e.getName().replace("/", "."))) { + return Stream.of(); + } else { + LOGGER.info("Inspecting " + e.getName()); + } + + ClassParser cp = new ClassParser(jarPath, e.getName()); + return getClassVisitor.apply(cp).start().methodCalls().stream(); + }) + .forEach( + p -> { + /* Create edges between nodes */ + calls.putIfAbsent((p.first), new HashSet<>()); + calls.get(p.first).add(p.second); + }); + } +} diff --git a/src/main/java/gr/gousiosg/javacg/stat/graph/Utilities.java b/src/main/java/gr/gousiosg/javacg/stat/graph/Utilities.java new file mode 100644 index 00000000..e4683d25 --- /dev/null +++ b/src/main/java/gr/gousiosg/javacg/stat/graph/Utilities.java @@ -0,0 +1,130 @@ +package gr.gousiosg.javacg.stat.graph; + +import gr.gousiosg.javacg.stat.JCallGraph; +import gr.gousiosg.javacg.stat.coverage.ColoredNode; +import gr.gousiosg.javacg.stat.support.IgnoredConstants; +import org.jgrapht.Graph; +import org.jgrapht.graph.DefaultEdge; +import org.jgrapht.nio.Attribute; +import org.jgrapht.nio.DefaultAttribute; +import org.jgrapht.nio.dot.DOTExporter; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.util.*; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import static gr.gousiosg.javacg.stat.graph.Constants.*; + +public class Utilities { + + private static final Logger LOGGER = LoggerFactory.getLogger(Utilities.class); + + public static Map nodeMap(Set nodes) { + return nodes.stream().collect(Collectors.toMap(ColoredNode::getLabel, node -> node)); + } + + /** + * Writes a graph to a file `name.dot` + * + * @param graph the graph + * @param exporter the exporter that will write the graph to a file + * @param path the file to use + * @param the type of the elements in the graph + */ + public static void writeGraph( + Graph graph, + DOTExporter exporter, + String path) { + LOGGER.info("Attempting to store callgraph..."); + + /* Write to .dot file in output directory */ + try { + Writer writer = new FileWriter(path); + exporter.exportGraph(graph, writer); + LOGGER.info("Graph written to " + path + "!"); + } catch (IOException e) { + LOGGER.error("Unable to write callgraph to " + path); + } + } + + /** + * Formats a vertex to be valid in the dot language + * + * @param vertex + * @return the formatted vertex + */ + private static String dotFormat(String vertex) { + return DOT_NODE_DELIMITER + vertex + DOT_NODE_DELIMITER; + } + + public static DOTExporter defaultExporter() { + DOTExporter exporter = new DOTExporter<>(id -> id); + exporter.setGraphAttributeProvider(defaultGraphAttributes()); + exporter.setVertexAttributeProvider( + (v) -> { + Map map = new LinkedHashMap<>(); + map.put(LABEL, DefaultAttribute.createAttribute(dotFormat(v))); + return map; + }); + exporter.setVertexIdProvider(Utilities::dotFormat); + return exporter; + } + + public static DOTExporter coloredExporter() { + DOTExporter exporter = new DOTExporter<>(ColoredNode::getLabel); + exporter.setGraphAttributeProvider(defaultGraphAttributes()); + exporter.setVertexAttributeProvider( + (v) -> { + Map map = new LinkedHashMap<>(); + map.put(LABEL, DefaultAttribute.createAttribute(dotFormat(v.getLabel()))); + map.put(STYLE, DefaultAttribute.createAttribute(FILLED)); + map.put(FILLCOLOR, DefaultAttribute.createAttribute(v.getColor())); + return map; + }); + exporter.setVertexIdProvider(v -> dotFormat(v.getLabel())); + return exporter; + } + + protected static void putIfAbsent(Graph graph, String vertex) { + if (!graph.containsVertex(vertex)) { + graph.addVertex(vertex); + } + } + + protected static boolean shouldIgnoreEntry(String entry) { + return IgnoredConstants.IGNORED_CALLING_PACKAGES.stream().anyMatch(entry::startsWith); + } + + protected static Stream enumerationAsStream(Enumeration e) { + return StreamSupport.stream( + Spliterators.spliteratorUnknownSize( + new Iterator() { + public T next() { + return e.nextElement(); + } + + public boolean hasNext() { + return e.hasMoreElements(); + } + }, + Spliterator.ORDERED), + false); + } + + private static Supplier> defaultGraphAttributes() { + return () -> { + Map map = new LinkedHashMap<>(); + map.put( + RANK_VERTICAL_SEPARATION, DefaultAttribute.createAttribute(VERTICAL_SEPARATION_VALUE)); + map.put(RANK_DIRECTION, DefaultAttribute.createAttribute(LEFT_TO_RIGHT)); + return map; + }; + } +} diff --git a/src/main/java/gr/gousiosg/javacg/stat/support/Arguments.java b/src/main/java/gr/gousiosg/javacg/stat/support/Arguments.java index 22399dd2..88a15939 100644 --- a/src/main/java/gr/gousiosg/javacg/stat/support/Arguments.java +++ b/src/main/java/gr/gousiosg/javacg/stat/support/Arguments.java @@ -12,7 +12,6 @@ public class Arguments { private static final Logger LOGGER = LoggerFactory.getLogger(Arguments.class); - private static final String WRAPPER = "\""; private static final String XML_SUFFIX = ".xml"; private static final String JAR_SUFFIX = ".jar"; private static final String JAR_INPUT = "j"; @@ -37,9 +36,10 @@ public class Arguments { /** * Parse command line args into variables + * * @param args the command line args */ - public Arguments (String[] args) { + public Arguments(String[] args) { LOGGER.info("Parsing command line arguments..."); /* Setup cmdline argument parsing */ @@ -51,12 +51,12 @@ public Arguments (String[] args) { try { cmd = parser.parse(options, args); - /* Parse JARs */ + /* Parse JARs */ if (cmd.hasOption(JAR_INPUT)) { jarPaths.addAll(Arrays.asList(cmd.getOptionValues(JAR_INPUT))); } - /* Parse coverage file */ + /* Parse coverage file */ if (cmd.hasOption(COVERAGE_INPUT)) { String coverageFile = cmd.getOptionValue(COVERAGE_INPUT); if (!coverageFile.endsWith(XML_SUFFIX)) { @@ -69,17 +69,7 @@ public Arguments (String[] args) { /* Parse entry point */ if (cmd.hasOption(ENTRYPOINT_INPUT)) { String ep = cmd.getOptionValue(ENTRYPOINT_INPUT); - - if (!ep.startsWith(WRAPPER)) { - ep = WRAPPER + ep; - } - - if (!ep.endsWith(WRAPPER)) { - ep = ep + WRAPPER; - } - LOGGER.info("Entry Point: " + ep); - this.maybeEntryPoint = Optional.of(ep); } @@ -90,7 +80,8 @@ public Arguments (String[] args) { /* Validate output filename */ if (!name.matches("^[a-zA-Z0-9_]*$")) { LOGGER.error("---> " + name + " <---"); - LOGGER.error("Please specify a valid name (letters, numbers, underscores) for the output. Do not include filetype!"); + LOGGER.error( + "Please specify a valid name (letters, numbers, underscores) for the output. Do not include filetype!"); System.exit(1); } @@ -124,75 +115,82 @@ public Arguments (String[] args) { LOGGER.error("Error parsing command-line arguments: " + pe.getMessage()); LOGGER.error("Please, follow the instructions below:"); HelpFormatter formatter = new HelpFormatter(); - formatter.printHelp( "Log messages to sequence diagrams converter", options); + formatter.printHelp("Log messages to sequence diagrams converter", options); System.exit(1); } /* Transform jar paths into (path, file) pairs */ jarPaths.stream() - .map(path -> { - if (!path.endsWith(JAR_SUFFIX)) { - LOGGER.error("---> " + path + " <---"); - LOGGER.error("Path should end in file of type .jar!"); - System.exit(1); - } - - File file = new File(path); - if (!file.exists()) { - LOGGER.error("JAR Path " + path + " doesn't exist!"); - System.exit(1); - } - - LOGGER.info("Found JAR: " + path); - return new Pair<>(path, file); - }) + .map( + path -> { + if (!path.endsWith(JAR_SUFFIX)) { + LOGGER.error("---> " + path + " <---"); + LOGGER.error("Path should end in file of type .jar!"); + System.exit(1); + } + + File file = new File(path); + if (!file.exists()) { + LOGGER.error("JAR Path " + path + " doesn't exist!"); + System.exit(1); + } + + LOGGER.info("Found JAR: " + path); + return new Pair<>(path, file); + }) .forEach(this.jars::add); } private static Options getOptions() { Options options = new Options(); - options.addOption(Option.builder(JAR_INPUT) - .longOpt(JAR_INPUT_LONG) - .hasArg(true) - .desc("[REQUIRED] specify one or more paths to JARs to analyze") - .required(true) - .build()); - - options.addOption(Option.builder(OUTPUT_NAME) - .longOpt(OUTPUT_NAME_LONG) - .hasArg(true) - .desc("[OPTIONAL] specify an output name for the graph") - .required(false) - .build()); - - options.addOption(Option.builder(DEPTH_INPUT) - .longOpt(DEPTH_INPUT_LONG) - .hasArg(true) - .desc("[OPTIONAL] specify a depth to explore graph to") - .required(false) - .build()); - - options.addOption(Option.builder(COVERAGE_INPUT) - .longOpt(COVERAGE_INPUT_LONG) - .hasArg(true) - .desc("[OPTIONAL] specify the coverage to apply to the reachability graph") - .required(false) - .build()); - - options.addOption(Option.builder(ENTRYPOINT_INPUT) - .longOpt(ENTRYPOINT_INPUT_LONG) - .hasArg(true) - .desc("[OPTIONAL] specify an entry point into the graph") - .required(false) - .build()); - - options.addOption(Option.builder(ANCESTRY_INPUT) - .longOpt(ANCESTRY_INPUT_LONG) - .hasArg(true) - .desc("[OPTIONAL] specify a depth to traverse the ancestry of an entrypoint") - .required(false) - .build()); + options.addOption( + Option.builder(JAR_INPUT) + .longOpt(JAR_INPUT_LONG) + .hasArg(true) + .desc("[REQUIRED] specify one or more paths to JARs to analyze") + .required(true) + .build()); + + options.addOption( + Option.builder(OUTPUT_NAME) + .longOpt(OUTPUT_NAME_LONG) + .hasArg(true) + .desc("[OPTIONAL] specify an output name for the graph") + .required(false) + .build()); + + options.addOption( + Option.builder(DEPTH_INPUT) + .longOpt(DEPTH_INPUT_LONG) + .hasArg(true) + .desc("[OPTIONAL] specify a depth to explore graph to") + .required(false) + .build()); + + options.addOption( + Option.builder(COVERAGE_INPUT) + .longOpt(COVERAGE_INPUT_LONG) + .hasArg(true) + .desc("[OPTIONAL] specify the coverage to apply to the reachability graph") + .required(false) + .build()); + + options.addOption( + Option.builder(ENTRYPOINT_INPUT) + .longOpt(ENTRYPOINT_INPUT_LONG) + .hasArg(true) + .desc("[OPTIONAL] specify an entry point into the graph") + .required(false) + .build()); + + options.addOption( + Option.builder(ANCESTRY_INPUT) + .longOpt(ANCESTRY_INPUT_LONG) + .hasArg(true) + .desc("[OPTIONAL] specify a depth to traverse the ancestry of an entrypoint") + .required(false) + .build()); return options; } diff --git a/src/main/java/gr/gousiosg/javacg/stat/support/BuildArguments.java b/src/main/java/gr/gousiosg/javacg/stat/support/BuildArguments.java new file mode 100644 index 00000000..2d83bbbf --- /dev/null +++ b/src/main/java/gr/gousiosg/javacg/stat/support/BuildArguments.java @@ -0,0 +1,188 @@ +package gr.gousiosg.javacg.stat.support; + +import gr.gousiosg.javacg.dyn.Pair; +import org.apache.commons.cli.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.nio.file.Path; +import java.util.*; + +public class BuildArguments { + + private static final Logger LOGGER = LoggerFactory.getLogger(BuildArguments.class); + private static final String JAR_SUFFIX = ".jar"; + private static final String JAR_INPUT = "j"; + private static final String JAR_INPUT_LONG = "jarPath"; + private static final String OUTPUT_NAME = "o"; + private static final String OUTPUT_NAME_LONG = "output"; + private static final String TEST_JAR_INPUT = "t"; + private static final String TEST_JAR_INPUT_LONG = "testJarPath"; + + private static final String CONFIG_NAME = "c"; + + private static final String CONFIG_NAME_LONG = "config"; + + private final List> jars = new ArrayList<>(); + private Optional maybeOutput = Optional.empty(); + + private Optional> maybeTestJar = Optional.empty(); + + private Optional maybeConfig = Optional.empty(); + + private final static String jarBasePath = "artifacts/output/"; + + /** + * Parse command line args into variables + * + * @param args the command line args + */ + public BuildArguments(String[] args) { + LOGGER.info("Parsing command line arguments..."); + /* Setup cmdline argument parsing */ + Set jarPaths = new HashSet<>(); + CommandLineParser parser = new DefaultParser(); + Options options = getOptions(); + CommandLine cmd; + try { + cmd = parser.parse(options, args); + + /* Get from configuration */ + if (cmd.hasOption(CONFIG_NAME)) { + Optional rt = RepoTool.obtainTool(cmd.getOptionValue(CONFIG_NAME)); + + if (rt.isPresent()) { + String mainJar = getJarRelativePath(rt.get().getMainJar(), rt.get().getProjectDir()); + jarPaths.add(mainJar); + + String testJar = getJarRelativePath(rt.get().getTestJar(), rt.get().getProjectDir()); + Pair testJarPair = pathAndJarFile(testJar); + jars.add(testJarPair); + maybeTestJar = Optional.of(testJarPair); + } else { + LOGGER.error("Unable to obtain RepoTool."); + System.exit(1); + } + } else { + /* Parse JARs */ + if (cmd.hasOption(JAR_INPUT)) { + jarPaths.addAll(Arrays.asList(cmd.getOptionValues(JAR_INPUT))); + } + + /* Parse test JAR */ + if (cmd.hasOption(TEST_JAR_INPUT)) { + String testJarPath = cmd.getOptionValue(TEST_JAR_INPUT); + Pair testJarPair = pathAndJarFile(testJarPath); + jars.add(testJarPair); + maybeTestJar = Optional.of(testJarPair); + } + } + + /* Parse output name */ + if (cmd.hasOption(OUTPUT_NAME)) { + String name = cmd.getOptionValue(OUTPUT_NAME); + this.maybeOutput = Optional.of(name); + } + } catch (ParseException pe) { + LOGGER.error("Error parsing command-line arguments: " + pe.getMessage()); + LOGGER.error("Please, follow the instructions below:"); + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("Log messages to sequence diagrams converter", options); + System.exit(1); + } + /* Transform jar paths into (path, file) pairs */ + jarPaths.stream() + .map(this::pathAndJarFile) + .forEach(this.jars::add); + } + + private static String getJarRelativePath(String jar, String projectDir) { + return jar.charAt(0) == '/' ? jar : Path.of(jarBasePath, projectDir, jar).toString(); + } + + private static Options getOptions() { + Options options = new Options(); + OptionGroup configOrMainJar = new OptionGroup(); + + configOrMainJar.isRequired(); + + configOrMainJar.addOption( + Option.builder(CONFIG_NAME) + .longOpt(CONFIG_NAME_LONG) + .hasArg(true) + .desc("[REQUIRED if -"+JAR_INPUT+" not specified] specify an output name for the bytecode") + .required(false) + .build()); + + configOrMainJar.addOption( + Option.builder(JAR_INPUT) + .longOpt(JAR_INPUT_LONG) + .hasArg(true) + .desc("[REQUIRED if -"+CONFIG_NAME+"not specified] specify one or more paths to JARs to analyze") + .required(false) + .build()); + + options.addOptionGroup(configOrMainJar); + + options.addOption( + Option.builder(TEST_JAR_INPUT) + .longOpt(TEST_JAR_INPUT_LONG) + .hasArg(true) + .desc("[OPTIONAL] specify a path to the test JAR for a project") + .required(false) + .build()); + + options.addOption( + Option.builder(OUTPUT_NAME) + .longOpt(OUTPUT_NAME_LONG) + .hasArg(true) + .desc("[REQUIRED] specify an output name for the bytecode") + .required(true) + .build()); + return options; + } + + public List> getJars() { + return jars; + } + + public Optional maybeOutput() { + return maybeOutput; + } + + public Optional> getMaybeTestJar() { + return maybeTestJar; + } + + public Optional getMaybeConfig() { + return maybeConfig; + } + + // Make sure the path is a path to a jar file + private void validateJarSuffix(String jarPath) { + if (!jarPath.endsWith(JAR_SUFFIX)) { + LOGGER.error("---> " + jarPath + " <---"); + LOGGER.error("Path should end in file of type .jar!"); + System.exit(1); + } + } + + // Get a jar file from a path + private File getJarFile(String jarPath) { + File file = new File(jarPath); + if (!file.exists()) { + LOGGER.error("JAR Path " + jarPath + " doesn't exist!"); + System.exit(1); + } + LOGGER.info("Found JAR: " + jarPath); + return file; + } + + // Return a (path, file) pair + private Pair pathAndJarFile(String jarPath) { + validateJarSuffix(jarPath); + File file = getJarFile(jarPath); + return new Pair<>(jarPath, file); + } +} diff --git a/src/main/java/gr/gousiosg/javacg/stat/support/ClassHierarchyInspector.java b/src/main/java/gr/gousiosg/javacg/stat/support/ClassHierarchyInspector.java index c0f1465a..79343b75 100644 --- a/src/main/java/gr/gousiosg/javacg/stat/support/ClassHierarchyInspector.java +++ b/src/main/java/gr/gousiosg/javacg/stat/support/ClassHierarchyInspector.java @@ -8,22 +8,19 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; -import java.util.stream.Collectors; public class ClassHierarchyInspector { private static final Logger LOGGER = LoggerFactory.getLogger(ClassHierarchyInspector.class); /** - * { - * {@link Class} -> { - * {@link MethodSignatureUtil#namedMethodSignature(Method)} -> {@link Method} - * } - * } + * { {@link Class} -> { {@link MethodSignatureUtil#namedMethodSignature(Method)} -> {@link Method} + * } } */ Map, Map> classDeclaredMethods = new HashMap<>(); /** * Memoize the declared methods of a class hierarchy into {@link classDeclaredMethods} + * * @param clazz the {@link Class} to expand the hierarchy of */ private void loadHierarchy(Class clazz) { @@ -38,10 +35,12 @@ private void loadHierarchy(Class clazz) { // Memoize the declared methods of this class. // May contain overridden methods - Arrays.stream(clazz.getDeclaredMethods()).forEach(method -> { - String namedSignature = MethodSignatureUtil.namedMethodSignature(method); - classDeclaredMethods.get(clazz).put(namedSignature, method); - }); + Arrays.stream(clazz.getDeclaredMethods()) + .forEach( + method -> { + String namedSignature = MethodSignatureUtil.namedMethodSignature(method); + classDeclaredMethods.get(clazz).put(namedSignature, method); + }); // Traverse the hierarchy and load it Optional> maybeParent = Optional.ofNullable(clazz.getSuperclass()); @@ -52,9 +51,9 @@ private void loadHierarchy(Class clazz) { } /** - * - * @param clazz a {@link Class} - * @param namedMethodSignature a method signature resembling {@link MethodSignatureUtil#namedMethodSignature(Method)} + * @param clazz a {@link Class} + * @param namedMethodSignature a method signature resembling {@link + * MethodSignatureUtil#namedMethodSignature(Method)} * @return a {@link Optional} */ public Optional getTopLevelSignature(Class clazz, String namedMethodSignature) { @@ -62,7 +61,8 @@ public Optional getTopLevelSignature(Class clazz, String namedMethodS // Ensure the hierarchy is loaded loadHierarchy(clazz); while (clazz != null) { - if (classDeclaredMethods.containsKey(clazz) && classDeclaredMethods.get(clazz).containsKey(namedMethodSignature)) { + if (classDeclaredMethods.containsKey(clazz) + && classDeclaredMethods.get(clazz).containsKey(namedMethodSignature)) { // Retrieve the method associated with `clazz` and `namedMethodSignature` return Optional.of(classDeclaredMethods.get(clazz).get(namedMethodSignature)); } @@ -70,7 +70,8 @@ public Optional getTopLevelSignature(Class clazz, String namedMethodS clazz = clazz.getSuperclass(); } } catch (Exception | NoClassDefFoundError e) { - LOGGER.error("Unable to find method " + namedMethodSignature + " in class " + clazz.getName()); + LOGGER.error( + "Unable to find method " + namedMethodSignature + " in class " + clazz.getName()); } return Optional.empty(); } diff --git a/src/main/java/gr/gousiosg/javacg/stat/support/GitArguments.java b/src/main/java/gr/gousiosg/javacg/stat/support/GitArguments.java new file mode 100644 index 00000000..c7549c7b --- /dev/null +++ b/src/main/java/gr/gousiosg/javacg/stat/support/GitArguments.java @@ -0,0 +1,49 @@ +package gr.gousiosg.javacg.stat.support; + +import org.apache.commons.cli.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Optional; + +public class GitArguments { + private static final Logger LOGGER = LoggerFactory.getLogger(GitArguments.class); + private static final String CONFIG_NAME = "c"; + private static final String CONFIG_NAME_LONG = "config"; + private Optional maybeConfig = Optional.empty(); + + public GitArguments(String[] args){ + LOGGER.info("Parsing command line arguments..."); + CommandLineParser parser = new DefaultParser(); + Options options = getOptions(); + CommandLine cmd; + try{ + cmd = parser.parse(options, args); + if(cmd.hasOption(CONFIG_NAME)){ + String configFile = cmd.getOptionValue(CONFIG_NAME); + maybeConfig = Optional.of(configFile); + } + } + catch(ParseException e){ + LOGGER.error("Configuration file required to run 'git'"); + LOGGER.error("Please chose a valid configuration found inside 'artifacts/configs'"); + System.exit(1); + } + } + + private static Options getOptions() { + Options options = new Options(); + + options.addOption( + Option.builder(CONFIG_NAME) + .longOpt(CONFIG_NAME_LONG) + .hasArg(true) + .desc("[REQUIRED] specify configuration to test for coverage") + .required(true) + .build()); + + return options; + } + + public Optional maybeGetConfig(){ return maybeConfig; } +} diff --git a/src/main/java/gr/gousiosg/javacg/stat/support/IgnoredConstants.java b/src/main/java/gr/gousiosg/javacg/stat/support/IgnoredConstants.java index 2fa90099..06aa7793 100644 --- a/src/main/java/gr/gousiosg/javacg/stat/support/IgnoredConstants.java +++ b/src/main/java/gr/gousiosg/javacg/stat/support/IgnoredConstants.java @@ -1,6 +1,5 @@ package gr.gousiosg.javacg.stat.support; -import java.util.Collection; import java.util.Set; public class IgnoredConstants { @@ -8,33 +7,33 @@ public class IgnoredConstants { /** * Do not expand method calls with these names */ - public static final Set IGNORED_METHOD_NAMES = Set.of( - "", - "" - ); + public static final Set IGNORED_METHOD_NAMES = Set.of("", ""); /** - * Do not look into jar entries with these prefixes + * Do not look into jar entries with these prefixes */ - public static final Set IGNORED_CALLING_PACKAGES = Set.of( - "java.", - "javax.", - "javassist.", - "org.slf4j", - "org.apache", - "guru.", - "com.kitfox", - "org.reflections", - "org.webjars", - "net.arnx", - "com.google", - "io.cucumber", - "org.hamcrest", - "com.eclipsesource", - "org.checkerframework", - "org.antlr", - "org.jheaps", - "org.jgrapht", - "com.linkedin" - ); + public static final Set IGNORED_CALLING_PACKAGES = + Set.of( + "java.", + "javax.", + "javassist.", + "org.slf4j", + "org.apache", + "guru.", + "com.kitfox", + "org.reflections", + "org.webjars", + "net.arnx", + "com.google", + "io.cucumber", + "org.hamcrest", + "com.eclipsesource", + "org.checkerframework", + "org.antlr", + "org.jheaps", + "org.jgrapht", + "com.linkedin", + "it.unimi", + "freemarker.", + "com.martiansoftware"); } diff --git a/src/main/java/gr/gousiosg/javacg/stat/support/JarMetadata.java b/src/main/java/gr/gousiosg/javacg/stat/support/JarMetadata.java index be761b7c..4f33e80e 100644 --- a/src/main/java/gr/gousiosg/javacg/stat/support/JarMetadata.java +++ b/src/main/java/gr/gousiosg/javacg/stat/support/JarMetadata.java @@ -1,26 +1,50 @@ package gr.gousiosg.javacg.stat.support; +import org.apache.bcel.generic.Type; import org.reflections.Reflections; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.Serializable; import java.net.URLClassLoader; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; +import java.util.*; -public class JarMetadata { +public class JarMetadata implements Serializable { - private static final Logger LOGGER = LoggerFactory.getLogger(JarMetadata.class); - - private final URLClassLoader cl; - private final Reflections reflections; - private final ClassHierarchyInspector inspector = new ClassHierarchyInspector(); + private static transient final Logger LOGGER = LoggerFactory.getLogger(JarMetadata.class); + /** + * Keeps track of the line numbers at which a method call occurs. This is combined with Jacoco + * line number coverage. + * + *

(MethodSignature -> Set(File:LineNumber)) + */ + public final Map> impliedMethodCalls = new HashMap<>(); + /** + * Methods that are found in the jar for a program's tests + */ + public final Set testMethods = new HashSet<>(); + private transient final URLClassLoader cl; + private transient final Reflections reflections; + private transient final ClassHierarchyInspector inspector = new ClassHierarchyInspector(); + /** + * Methods that are generated from the type erasure process + */ private final Set bridgeMethods = new HashSet<>(); + /** + * Methods that are created during {@link gr.gousiosg.javacg.stat.MethodVisitor#expand(Class, + * String, Type[], Type, String)} + */ + private final Set concreteMethods = new HashSet<>(); + /** + * Methods result in a call to {@link gr.gousiosg.javacg.stat.MethodVisitor#expand(Class, String, + * Type[], Type, String)} + */ + private final Set virtualMethods = new HashSet<>(); /** * Wrapper class used for reflection on class hierarchies - * @param cl the {@link ClassLoader} containing all provided jars + * + * @param cl the {@link ClassLoader} containing all provided jars * @param reflections the {@link Reflections} using the {@link cl} classloader */ public JarMetadata(URLClassLoader cl, Reflections reflections) { @@ -31,9 +55,7 @@ public JarMetadata(URLClassLoader cl, Reflections reflections) { public Optional> getClass(String qualifiedName) { qualifiedName = qualifiedName.replace("/", "."); try { - return Optional.of( - Class.forName(qualifiedName, false, cl) - ); + return Optional.of(Class.forName(qualifiedName, false, cl)); } catch (NoClassDefFoundError | Exception e) { LOGGER.error("Unable to load class: " + qualifiedName); return Optional.empty(); @@ -48,11 +70,35 @@ public ClassHierarchyInspector getInspector() { return inspector; } - public void addBridgeMethod(String bridgeMethodSignature) { - bridgeMethods.add(bridgeMethodSignature); + public void addBridgeMethod(String methodSignature) { + bridgeMethods.add(methodSignature); + } + + public void addConcreteMethod(String methodSignature) { + concreteMethods.add(methodSignature); + } + + public void addVirtualMethod(String methodSignature) { + virtualMethods.add(methodSignature); + } + + public boolean containsConcreteMethod(String methodSignature) { + return concreteMethods.contains(methodSignature); + } + + public boolean containsVirtualMethod(String methodSignature) { + return virtualMethods.contains(methodSignature); } public Set getBridgeMethods() { - return bridgeMethods; + return new HashSet<>(bridgeMethods); + } + + public Set getConcreteMethods() { + return new HashSet<>(concreteMethods); + } + + public Set getVirtualMethods() { + return new HashSet<>(virtualMethods); } } diff --git a/src/main/java/gr/gousiosg/javacg/stat/support/MethodSignatureUtil.java b/src/main/java/gr/gousiosg/javacg/stat/support/MethodSignatureUtil.java index 58d1b933..51b71354 100644 --- a/src/main/java/gr/gousiosg/javacg/stat/support/MethodSignatureUtil.java +++ b/src/main/java/gr/gousiosg/javacg/stat/support/MethodSignatureUtil.java @@ -6,59 +6,57 @@ public class MethodSignatureUtil { - public static String fullyQualifiedMethodSignature(String className, String methodName, String methodDescriptor) { - return String.join(".", sanitizeClassName(className), methodName) + methodDescriptor; - } - - public static String fullyQualifiedMethodSignature(Class clazz, Method method) { - return fullyQualifiedMethodSignature( - fullyQualifiedClassName(clazz), - methodName(method), - methodDescriptor(method) - ); - } - - public static String fullyQualifiedMethodSignature(Method method) { - return fullyQualifiedMethodSignature( - fullyQualifiedClassName(method.getDeclaringClass()), - methodName(method), - methodDescriptor(method) - ); - } - - public static String fullyQualifiedMethodSignature(String className, String methodName, Type[] methodArgumentTypes, Type methodReturnType) { - return fullyQualifiedMethodSignature( - sanitizeClassName(className), - methodName, - methodDescriptor(methodArgumentTypes, methodReturnType) - ); - } - - public static String fullyQualifiedClassName(Class clazz) { - return sanitizeClassName(clazz.getName()); - } - - public static String namedMethodSignature(String methodName, Type[] methodArgumentTypes, Type methodReturnType) { - return methodName + methodDescriptor(methodArgumentTypes, methodReturnType); - } - - public static String namedMethodSignature(Method m) { - return methodName(m) + methodDescriptor(m); - } - - public static String methodName(Method m) { - return m.getName(); - } - - public static String methodDescriptor(Method m) { - return Type.getSignature(m); - } - - public static String methodDescriptor(Type[] methodArgumentTypes, Type methodReturnType) { - return Type.getMethodSignature(methodReturnType, methodArgumentTypes); - } - - protected static String sanitizeClassName(String className) { - return className.replace("/", ".").replace("$", "."); - } + public static String fullyQualifiedMethodSignature( + String className, String methodName, String methodDescriptor) { + return String.join(".", sanitizeClassName(className), methodName) + methodDescriptor; + } + + public static String fullyQualifiedMethodSignature(Class clazz, Method method) { + return fullyQualifiedMethodSignature( + fullyQualifiedClassName(clazz), methodName(method), methodDescriptor(method)); + } + + public static String fullyQualifiedMethodSignature(Method method) { + return fullyQualifiedMethodSignature( + fullyQualifiedClassName(method.getDeclaringClass()), + methodName(method), + methodDescriptor(method)); + } + + public static String fullyQualifiedMethodSignature( + String className, String methodName, Type[] methodArgumentTypes, Type methodReturnType) { + return fullyQualifiedMethodSignature( + sanitizeClassName(className), + methodName, + methodDescriptor(methodArgumentTypes, methodReturnType)); + } + + public static String fullyQualifiedClassName(Class clazz) { + return sanitizeClassName(clazz.getName()); + } + + public static String namedMethodSignature( + String methodName, Type[] methodArgumentTypes, Type methodReturnType) { + return methodName + methodDescriptor(methodArgumentTypes, methodReturnType); + } + + public static String namedMethodSignature(Method m) { + return methodName(m) + methodDescriptor(m); + } + + public static String methodName(Method m) { + return m.getName(); + } + + public static String methodDescriptor(Method m) { + return Type.getSignature(m); + } + + public static String methodDescriptor(Type[] methodArgumentTypes, Type methodReturnType) { + return Type.getMethodSignature(methodReturnType, methodArgumentTypes); + } + + protected static String sanitizeClassName(String className) { + return className.replace("/", ".").replace("$", "."); + } } diff --git a/src/main/java/gr/gousiosg/javacg/stat/support/RepoTool.java b/src/main/java/gr/gousiosg/javacg/stat/support/RepoTool.java new file mode 100644 index 00000000..9a621db1 --- /dev/null +++ b/src/main/java/gr/gousiosg/javacg/stat/support/RepoTool.java @@ -0,0 +1,267 @@ +package gr.gousiosg.javacg.stat.support; + +import gr.gousiosg.javacg.dyn.Pair; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.yaml.snakeyaml.Yaml; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.*; +import java.nio.file.*; +import java.util.*; + +public class RepoTool { + + private static final Logger LOGGER = LoggerFactory.getLogger(RepoTool.class); + final private String name; + final private String URL; + final private String checkoutID; + final private String patchName; + final private String subProject; + final private String mvnOptions; + private List> properties; + private Git git; + final private String timeStamp; + + final private String mainJar; + + final private String testJar; + + + private RepoTool(String name, String URL, String checkoutID, String patchName, String subProject, String mvnOptions, String mainJar, String testJar){ + this.name = name; + this.URL = URL; + this.checkoutID = checkoutID; + this.patchName = patchName; + this.subProject = subProject; + this.mvnOptions = mvnOptions; + this.mainJar = mainJar; + this.testJar = testJar; + + this.timeStamp = String.valueOf(java.time.LocalDateTime.now()).replace(':', '_'); + } + + public RepoTool(String name) throws FileNotFoundException { + this(name, String.valueOf(java.time.LocalDateTime.now()).replace(':', '_')); + } + + public RepoTool(String name, String timeStamp) throws FileNotFoundException { + // @todo Perhaps using objects to store configuration data so we don't have to have unchecked casts e.g. https://www.baeldung.com/java-snake-yaml + + Yaml yaml = new Yaml(); + InputStream inputStream = new FileInputStream("artifacts/configs/" + name + "/" + name + ".yaml"); + Map data = yaml.load(inputStream); + + this.name = name; + URL = (String) data.get("URL"); + checkoutID = (String) data.get("checkoutID"); + patchName = (String) data.get("patchName"); + subProject = (String) data.getOrDefault("subProject", ""); + mvnOptions = (String) data.getOrDefault("mvnOptions", ""); + properties = (List>) data.get("properties"); + mainJar = (String) data.getOrDefault("mainJar", new ArrayList<>()); + testJar = (String) data.getOrDefault("testJar", Optional.empty()); + + this.timeStamp = timeStamp; + } + + public void cloneRepo() throws GitAPIException, JGitInternalException { + this.git = Git.cloneRepository() + .setDirectory(new File(name)) + .setURI(URL) + .call(); + this.git.checkout() + .setName(checkoutID) + .call(); + } + + public void applyPatch() throws IOException, InterruptedException { + ProcessBuilder pb = new ProcessBuilder(); + if(isWindows()) + pb.command("cmd.exe", "/c", "git", "apply", patchName, "--directory", name); + else + pb.command("bash", "-c", "patch -p1 -d " + name + " < " + patchName); + Process process = pb.start(); + process.waitFor(); + } + + public void buildJars() throws IOException, InterruptedException { + ProcessBuilder pb = new ProcessBuilder(); + if(isWindows()) + pb.command("cmd.exe", "/c", "mvn", "install", "-DskipTests"); + else + pb.command("bash", "-c", "mvn install -DskipTests"); + pb.directory(new File(this.name)); + Process process = pb.start(); + BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while((line = br.readLine()) != null) + LOGGER.info(line); + process.waitFor(); + copyJars(); + } + + public void testProperty(String property) throws IOException, InterruptedException { + ProcessBuilder pb = new ProcessBuilder(); + if(isWindows()) + pb.command("cmd.exe", "/c", "mvn", "test", mvnOptions, "-Dtest=" + property); + else + pb.command("bash", "-c", "mvn test " + mvnOptions + " -Dtest=" + property); + pb.directory(new File(this.name)); + long start = System.nanoTime(); + Process process = pb.start(); + BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while((line = br.readLine()) != null) + LOGGER.info(line); + process.waitFor(); + long end = System.nanoTime(); + moveJacoco(property, end - start); + } + + public void cleanTarget() throws IOException, InterruptedException { + LOGGER.info("-------Cleaning target---------"); + ProcessBuilder pb = new ProcessBuilder(); + if(isWindows()) + pb.command("cmd.exe", "/c", "mvn", "clean"); + else + pb.command("bash", "-c", "mvn clean"); + pb.directory(new File(this.name)); + Process process = pb.start(); + BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while((line = br.readLine()) != null) + LOGGER.info(line); + process.waitFor(); + } + + public List> obtainCoverageFilesAndEntryPoints(){ + List> coverageFiles = new LinkedList<>(); + for(Map m : properties){ + String projectDir = getProjectDir(); + if(m.get("entryPoint") instanceof String){ + if(!projectDir.contains("/")) + coverageFiles.add(new Pair<>("artifacts/results/" + projectDir + "/"+ projectDir + timeStamp + "/" + m.get("name") + ".xml", m.get("entryPoint"))); + else + coverageFiles.add(new Pair<>("artifacts/results/" + projectDir + timeStamp + "/" + m.get("name") + ".xml", m.get("entryPoint"))); + } + else{ + if(!projectDir.contains("/")) + coverageFiles.add(new Pair("artifacts/results/" + projectDir + "/" + projectDir + timeStamp + "/" + m.get("name") + ".xml", (ArrayList) m.get("entryPoint"))); + else + coverageFiles.add(new Pair("artifacts/results/" + projectDir + timeStamp + "/" + m.get("name") + ".xml", (ArrayList) m.get("entryPoint"))); + } + } + + return coverageFiles; + } + + public String getTestJar() { + return testJar; + } + + public String getMainJar() { + return mainJar; + } + + public static Optional obtainTool(String folderName){ + try { + Yaml yaml = new Yaml(); + InputStream inputStream = new FileInputStream("artifacts/configs/" + folderName + "/" + folderName + ".yaml"); + Map data = yaml.load(inputStream); + return Optional.of(new RepoTool(data.get("name"), data.get("URL"), data.get("checkoutID"), data.get("patchName"), data.getOrDefault("subProject", ""), data.getOrDefault("mvnOptions", ""), data.getOrDefault("mainJar", ""), data.getOrDefault("testJar", ""))); + } + catch(IOException e){ + LOGGER.error("IOException: " + e.getMessage()); + } + LOGGER.error("Could not obtain yaml file!"); + return Optional.empty(); + } + + private void copyJars() throws IOException { + Path sourceDir = Paths.get(System.getProperty("user.dir"), getProjectDir(), "target"); + Path targetDir = Paths.get(System.getProperty("user.dir"), "artifacts", "output", getProjectDir()); + File validateDirectory = targetDir.toFile(); + if(!validateDirectory.exists()) + validateDirectory.mkdirs(); + moveFiles(sourceDir, targetDir, "*.jar"); + } + + private void moveFiles(Path sourceDir, Path targetDir, String glob) throws IOException { + try (DirectoryStream dirStream = Files.newDirectoryStream(sourceDir, glob)) { + for (Path source: dirStream) { + Files.move( + source, + targetDir.resolve(source.getFileName()), + StandardCopyOption.REPLACE_EXISTING); + } + } + } + + private void copyFiles(Path sourceDir, Path targetDir) throws IOException { + try (DirectoryStream dirStream = Files.newDirectoryStream(sourceDir)) { + for (Path source: dirStream) { + Files.copy( + source, + targetDir.resolve(source.getFileName()), + StandardCopyOption.REPLACE_EXISTING); + } + } + } + + + private void moveJacoco(String property, long timeElapsed) throws IOException{ + String projectDir = getProjectDir(); + String directoryPath = System.getProperty("user.dir") + "/artifacts/results/" + projectDir + timeStamp; + if(!projectDir.contains("/")) + directoryPath = System.getProperty("user.dir") + "/artifacts/results/" + projectDir + "/" + projectDir + timeStamp; + String jacocoPath = System.getProperty("user.dir") + "/" + projectDir + "/target/site/jacoco/jacoco.xml"; + String jacocoTargetPath = directoryPath + "/" + property + ".xml"; + String statisticsPath = System.getProperty("user.dir") + "/" + projectDir + "/target/site/jacoco/index.html"; + String statisticsTargetPath = directoryPath + "/" + property + ".html"; + if(projectDir.contains("/")){ + String [] directories = projectDir.split("/"); + String rootDirectoryPath = System.getProperty("user.dir") + "/artifacts/results/" + directories[0]; + File rootDir = new File(rootDirectoryPath); + if(!rootDir.exists()) + rootDir.mkdir(); + } + File directory = new File(directoryPath); + directory.mkdir(); + Files.move( + Paths.get(jacocoPath), + Paths.get(jacocoTargetPath), + StandardCopyOption.REPLACE_EXISTING); + Files.move( + Paths.get(statisticsPath), + Paths.get(statisticsTargetPath), + StandardCopyOption.REPLACE_EXISTING); + double timeElapsedInSeconds = (double) timeElapsed / 1_000_000_000; + try (FileWriter fileWriter = new FileWriter(statisticsTargetPath, true); BufferedWriter bufferedWriter = new BufferedWriter(fileWriter)) { + bufferedWriter.append("

Total Time Elapsed: ").append(String.valueOf(timeElapsedInSeconds)).append(" seconds

"); + bufferedWriter.flush(); + } + } + + public void moveOutput() throws Exception { + String projectDirectory = getProjectDir(); + if(!projectDirectory.contains("/")) + projectDirectory = projectDirectory + "/" + projectDirectory; + copyFiles( + Paths.get(System.getProperty("user.dir"), "/output"), // src + Paths.get(System.getProperty("user.dir"), "/artifacts/results/", projectDirectory + timeStamp) // dst + ); + } + + private boolean isWindows() { + return System.getProperty("os.name") + .toLowerCase().startsWith("windows"); + } + + public String getProjectDir() { + return (subProject.equals("")) ? name : (name + "/" + subProject); + } + + public String getSubProject() { return subProject; } +} diff --git a/src/main/java/gr/gousiosg/javacg/stat/support/TestArguments.java b/src/main/java/gr/gousiosg/javacg/stat/support/TestArguments.java new file mode 100644 index 00000000..e407e5b6 --- /dev/null +++ b/src/main/java/gr/gousiosg/javacg/stat/support/TestArguments.java @@ -0,0 +1,132 @@ +package gr.gousiosg.javacg.stat.support; + +import org.apache.commons.cli.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +public class TestArguments { + + private static final Logger LOGGER = LoggerFactory.getLogger(TestArguments.class); + + private static final String FILE_NAME = "f"; + private static final String FILE_NAME_LONG = "file"; + private static final String CONFIG_NAME = "c"; + private static final String CONFIG_NAME_LONG = "config"; + private static final String DEPTH_INPUT = "d"; + private static final String DEPTH_INPUT_LONG = "depth"; + private static final String ANCESTRY_INPUT = "a"; + private static final String ANCESTRY_INPUT_LONG = "ancestry"; + + private Optional bytecodeFile; + private Optional maybeConfig = Optional.empty(); + private Optional maybeDepth = Optional.empty(); + private Optional maybeAncestry = Optional.empty(); + + public TestArguments(String[] args){ + LOGGER.info("Parsing command line arguments..."); + + /* Setup cmdline argument parsing */ + CommandLineParser parser = new DefaultParser(); + Options options = getOptions(); + CommandLine cmd; + + try{ + cmd = parser.parse(options, args); + + /*Parse bytecode file */ + if (cmd.hasOption(FILE_NAME)){ + bytecodeFile = Optional.of(cmd.getOptionValue(FILE_NAME)); + } + + if(cmd.hasOption(CONFIG_NAME)){ + this.maybeConfig = Optional.of(cmd.getOptionValue(CONFIG_NAME)); + } + /* Parse ancestry */ + if (cmd.hasOption(ANCESTRY_INPUT)) { + String val = cmd.getOptionValue(ANCESTRY_INPUT); + try { + this.maybeAncestry = Optional.of(Integer.parseInt(val)); + } catch (NumberFormatException e) { + LOGGER.error("---> " + val + " <---"); + LOGGER.error("Please specify a valid integer for depth!"); + System.exit(1); + } + } + + /* Parse depth */ + if (cmd.hasOption(DEPTH_INPUT)) { + String val = cmd.getOptionValue(DEPTH_INPUT); + try { + this.maybeDepth = Optional.of(Integer.parseInt(val)); + } catch (NumberFormatException e) { + LOGGER.error("---> " + val + " <---"); + LOGGER.error("Please specify a valid integer for depth!"); + System.exit(1); + } + } + } + catch(ParseException pe){ + LOGGER.error("Error parsing command-line arguments: " + pe.getMessage()); + LOGGER.error("Please, follow the instructions below:"); + HelpFormatter formatter = new HelpFormatter(); + formatter.printHelp("Log messages to sequence diagrams converter", options); + System.exit(1); + } + + } + + private static Options getOptions() { + Options options = new Options(); + options.addOption( + Option.builder(CONFIG_NAME) + .longOpt(CONFIG_NAME_LONG) + .hasArg(true) + .desc("[REQUIRED] specify the config folder") + .required(true) + .build()); + + options.addOption( + Option.builder(FILE_NAME) + .longOpt(FILE_NAME_LONG) + .hasArg(true) + .desc("[REQUIRED] specify the bytecode file") + .required(true) + .build()); + + + options.addOption( + Option.builder(DEPTH_INPUT) + .longOpt(DEPTH_INPUT_LONG) + .hasArg(true) + .desc("[OPTIONAL] specify a depth to explore graph to") + .required(false) + .build()); + + + options.addOption( + Option.builder(ANCESTRY_INPUT) + .longOpt(ANCESTRY_INPUT_LONG) + .hasArg(true) + .desc("[OPTIONAL] specify a depth to traverse the ancestry of an entrypoint") + .required(false) + .build()); + + return options; + } + + public Optional maybeBytecodeFile(){ return bytecodeFile; } + + public Optional maybeDepth() { + return maybeDepth; + } + + public Optional maybeGetConfig() {return maybeConfig;} + + public Optional maybeAncestry() { + return maybeAncestry; + } +} diff --git a/src/main/java/gr/gousiosg/javacg/stat/support/coverage/JacocoCoverage.java b/src/main/java/gr/gousiosg/javacg/stat/support/coverage/JacocoCoverage.java deleted file mode 100644 index b06336c8..00000000 --- a/src/main/java/gr/gousiosg/javacg/stat/support/coverage/JacocoCoverage.java +++ /dev/null @@ -1,92 +0,0 @@ -package gr.gousiosg.javacg.stat.support.coverage; - -import gr.gousiosg.javacg.stat.GraphUtils; -import gr.gousiosg.javacg.stat.support.MethodSignatureUtil; -import org.jgrapht.Graph; -import org.jgrapht.graph.DefaultEdge; -import org.objectweb.asm.Type; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.xml.sax.SAXException; - -import javax.xml.bind.JAXBElement; -import javax.xml.bind.JAXBException; -import javax.xml.parsers.ParserConfigurationException; -import java.io.IOException; -import java.util.*; -import java.util.stream.Collectors; - -public class JacocoCoverage { - - private static final Logger LOGGER = LoggerFactory.getLogger(JacocoCoverage.class); - public static final String METHOD_TYPE = "METHOD"; - public static final String LINE_TYPE = "LINE"; - public static final String BRANCH_TYPE = "BRANCH"; - - private boolean hasCoverage = false; - - private Map methodCoverage = new HashMap<>(); - - /** - * Create a {@link JacocoCoverage} object - * @param maybeFilepath the JaCoCo coverage XML file to parse - */ - public JacocoCoverage(Optional maybeFilepath) throws IOException, ParserConfigurationException, JAXBException, SAXException { - - if (maybeFilepath.isPresent()) { - /* Convert the jacoco.xml file into a Report object */ - Report report = JacocoCoverageParser.getReport(maybeFilepath.get()); - - /* Iterate over all packages in report */ - for (Report.Package pkg : report.getPackage()) { - - /* Iterate over all classes in a package */ - for (Report.Package.Class clazz : pkg.getClazz()) { - - /* Find all methods in a class */ - List methods = clazz.getContent().stream() - .filter(s -> s instanceof JAXBElement) - .map(s -> (JAXBElement) s) - .filter(je -> je.getValue() instanceof Report.Package.Class.Method) - .map(je -> (Report.Package.Class.Method) je.getValue()) - .collect(Collectors.toList()); - - /* Store "covered" methods in methodCoverage */ - methods.forEach(method -> { - String qualifiedName = MethodSignatureUtil.fullyQualifiedMethodSignature( - clazz.getName(), - method.getName(), - method.getDesc()); - methodCoverage.putIfAbsent(GraphUtils.formatNode(qualifiedName), method); - }); - } - } - - /* Indicate that coverage has been applied */ - hasCoverage = true; - } - } - - public void applyCoverage(Graph graph) { - LOGGER.info("Applying coverage!"); - Map nodeMap = nodeMap(graph.vertexSet()); - nodeMap.keySet().forEach(node -> { - if (methodCoverage.containsKey(node)) { - Report.Package.Class.Method m = methodCoverage.get(node); - nodeMap.get(node).mark(m); - } else { - LOGGER.warn("Couldn't find coverage for " + node); - nodeMap.get(node).markMissing(); - } - }); - } - - private static Map nodeMap(Set nodes) { - return nodes.stream() - .collect(Collectors.toMap(ColoredNode::getLabel, node -> node)); - } - - public boolean hasCoverage() { - return hasCoverage; - } -} diff --git a/src/main/resources/jacoco-schema.xsd b/src/main/resources/jacoco-schema.xsd index 13ebf0f9..7d14389a 100644 --- a/src/main/resources/jacoco-schema.xsd +++ b/src/main/resources/jacoco-schema.xsd @@ -1,4 +1,5 @@ - + @@ -26,9 +27,12 @@ - - - + + + @@ -62,7 +66,7 @@ - + diff --git a/src/test/java/gr/gousiosg/javacg/stat/support/ClassHierarchyInspectorTest.java b/src/test/java/gr/gousiosg/javacg/stat/support/ClassHierarchyInspectorTest.java index 858450d8..3564b9f2 100644 --- a/src/test/java/gr/gousiosg/javacg/stat/support/ClassHierarchyInspectorTest.java +++ b/src/test/java/gr/gousiosg/javacg/stat/support/ClassHierarchyInspectorTest.java @@ -9,40 +9,46 @@ public class ClassHierarchyInspectorTest { - private ClassHierarchyInspector inspector; - - @Before - public void setup() { - inspector = new ClassHierarchyInspector(); - } - - /** - * B inherits "a" from A - */ - @Test - public void itFetchesTheTopLevelMethod() { - Method a = HierarchyHelper.A.class.getDeclaredMethods()[0]; - Optional maybeMethod = inspector.getTopLevelSignature(HierarchyHelper.B.class, MethodSignatureUtil.namedMethodSignature(a)); - Assert.assertTrue(maybeMethod.isPresent()); - Assert.assertEquals(a, maybeMethod.get()); - } - - @Test - public void itDetectsOverriddenMethodsPartOne() { - // with its own named method signature - Method overridden = HierarchyHelper.C.class.getDeclaredMethods()[0]; - Optional maybeMethod = inspector.getTopLevelSignature(HierarchyHelper.C.class, MethodSignatureUtil.namedMethodSignature(overridden)); - Assert.assertTrue(maybeMethod.isPresent()); - Assert.assertEquals(overridden, maybeMethod.get()); - } - - @Test - public void itDetectsOverriddenMethodsPartTwo() { - // with the parent classes' named method signatures - Method overridden = HierarchyHelper.C.class.getDeclaredMethods()[0]; - Method original = HierarchyHelper.A.class.getDeclaredMethods()[0]; - Optional maybeMethod = inspector.getTopLevelSignature(HierarchyHelper.C.class, MethodSignatureUtil.namedMethodSignature(original)); - Assert.assertTrue(maybeMethod.isPresent()); - Assert.assertEquals(overridden, maybeMethod.get()); - } + private ClassHierarchyInspector inspector; + + @Before + public void setup() { + inspector = new ClassHierarchyInspector(); + } + + /** + * B inherits "a" from A + */ + @Test + public void itFetchesTheTopLevelMethod() { + Method a = HierarchyHelper.A.class.getDeclaredMethods()[0]; + Optional maybeMethod = + inspector.getTopLevelSignature( + HierarchyHelper.B.class, MethodSignatureUtil.namedMethodSignature(a)); + Assert.assertTrue(maybeMethod.isPresent()); + Assert.assertEquals(a, maybeMethod.get()); + } + + @Test + public void itDetectsOverriddenMethodsPartOne() { + // with its own named method signature + Method overridden = HierarchyHelper.C.class.getDeclaredMethods()[0]; + Optional maybeMethod = + inspector.getTopLevelSignature( + HierarchyHelper.C.class, MethodSignatureUtil.namedMethodSignature(overridden)); + Assert.assertTrue(maybeMethod.isPresent()); + Assert.assertEquals(overridden, maybeMethod.get()); + } + + @Test + public void itDetectsOverriddenMethodsPartTwo() { + // with the parent classes' named method signatures + Method overridden = HierarchyHelper.C.class.getDeclaredMethods()[0]; + Method original = HierarchyHelper.A.class.getDeclaredMethods()[0]; + Optional maybeMethod = + inspector.getTopLevelSignature( + HierarchyHelper.C.class, MethodSignatureUtil.namedMethodSignature(original)); + Assert.assertTrue(maybeMethod.isPresent()); + Assert.assertEquals(overridden, maybeMethod.get()); + } } diff --git a/src/test/java/gr/gousiosg/javacg/stat/support/HierarchyHelper.java b/src/test/java/gr/gousiosg/javacg/stat/support/HierarchyHelper.java index f9510b33..4a0ee35c 100644 --- a/src/test/java/gr/gousiosg/javacg/stat/support/HierarchyHelper.java +++ b/src/test/java/gr/gousiosg/javacg/stat/support/HierarchyHelper.java @@ -1,22 +1,22 @@ package gr.gousiosg.javacg.stat.support; public class HierarchyHelper { - public static class A { - public Integer a(int a, String b) { - return Math.min(a, b.length()); + public static class A { + public Integer a(int a, String b) { + return Math.min(a, b.length()); + } } - } - public static class B extends A { - public String b(int a, String b) { - return b + Integer.toString(a); + public static class B extends A { + public String b(int a, String b) { + return b + Integer.toString(a); + } } - } - public static class C extends B { - @Override - public Integer a(int a, String b) { - return Math.max(a, b.length()); + public static class C extends B { + @Override + public Integer a(int a, String b) { + return Math.max(a, b.length()); + } } - } } diff --git a/src/test/java/gr/gousiosg/javacg/stat/support/MethodSignatureUtilTest.java b/src/test/java/gr/gousiosg/javacg/stat/support/MethodSignatureUtilTest.java index c1a23981..be6d2957 100644 --- a/src/test/java/gr/gousiosg/javacg/stat/support/MethodSignatureUtilTest.java +++ b/src/test/java/gr/gousiosg/javacg/stat/support/MethodSignatureUtilTest.java @@ -8,170 +8,175 @@ public class MethodSignatureUtilTest { - private static final String JACOCO_CLASS_NAME = "edu/uic/cs398/Book/BookFactory"; - private static final String JACOCO_METHOD_NAME = "getBook"; - private static final String JACOCO_METHOD_DESCRIPTOR = "(I)Ledu/uic/cs398/Book/Book;"; - private static final String EXPECTED_JACOCO_CONVERSION = "edu.uic.cs398.Book.BookFactory.getBook(I)Ledu/uic/cs398/Book/Book;"; - - /** - * These should all be equivalent - */ - @Test - public void testFullyQualifiedMethodNameEquivalence() { - // Get class and method - Class clazz = HierarchyHelper.B.class; - Method method = clazz.getDeclaredMethods()[0]; - String classname = MethodSignatureUtil.fullyQualifiedClassName(clazz); - String methodName = MethodSignatureUtil.methodName(method); - - // Get BCEL types - String signature = Type.getSignature(method); - Type[] methodArgumentTypes = Type.getArgumentTypes(signature); - Type methodReturnType = Type.getReturnType(signature); - - // test it once - String actual = MethodSignatureUtil.fullyQualifiedMethodSignature(classname, methodName, methodArgumentTypes, methodReturnType); - String expected = "gr.gousiosg.javacg.stat.support.HierarchyHelper.B.b(ILjava/lang/String;)Ljava/lang/String;"; - Assert.assertEquals(expected, actual); - - // test against another variant - String another = MethodSignatureUtil.fullyQualifiedMethodSignature(clazz, method); - Assert.assertEquals(another, actual); - - another = MethodSignatureUtil.fullyQualifiedMethodSignature(classname, methodName, MethodSignatureUtil.methodDescriptor(method)); - Assert.assertEquals(another, actual); - - another = MethodSignatureUtil.fullyQualifiedMethodSignature(method); - Assert.assertEquals(another, actual); - } - - /** - * These should all be equivalent - */ - @Test - public void testMethodDescriptorEquivalence() { - // Get class and method - Class clazz = HierarchyHelper.B.class; - Method method = clazz.getDeclaredMethods()[0]; - String methodName = MethodSignatureUtil.methodName(method); - - // Get BCEL types - String signature = Type.getSignature(method); - Type[] methodArgumentTypes = Type.getArgumentTypes(signature); - Type methodReturnType = Type.getReturnType(signature); - - // regular and named descriptors - String regularDescriptor = "(ILjava/lang/String;)Ljava/lang/String;"; - String namedDescriptor = "b" + regularDescriptor; - - - // test regular descriptors - String actual = MethodSignatureUtil.methodDescriptor(method); - Assert.assertEquals(regularDescriptor, actual); - - actual = MethodSignatureUtil.methodDescriptor(methodArgumentTypes, methodReturnType); - Assert.assertEquals(regularDescriptor, actual); - - // test named descriptors - actual = MethodSignatureUtil.namedMethodSignature(method); - Assert.assertEquals(namedDescriptor, actual); - - actual = MethodSignatureUtil.namedMethodSignature(methodName, methodArgumentTypes, methodReturnType); - Assert.assertEquals(namedDescriptor, actual); - } - - /** - * A Class and Method is properly resolved to the fully qualified method signature, e.g.: - * `gr.gousiosg.javacg.stat.support.HierarchyHelper.B.b(ILjava/lang/String;)Ljava/lang/String;` - */ - @Test - public void testClassAndMethodToFullyQualifiedMethodSignature() { - Method m = HierarchyHelper.B.class.getDeclaredMethods()[0]; - String actual = MethodSignatureUtil.fullyQualifiedMethodSignature(HierarchyHelper.B.class, m); - String expected = "gr.gousiosg.javacg.stat.support.HierarchyHelper.B.b(ILjava/lang/String;)Ljava/lang/String;"; - Assert.assertEquals(expected, actual); - } - - /** - * A method signature is properly resolved, e.g.: - * `b(ILjava/lang/String;)Ljava/lang/String;` - */ - @Test - public void testMethodToMethodSignature() { - Method m = HierarchyHelper.B.class.getDeclaredMethods()[0]; - String actual = MethodSignatureUtil.namedMethodSignature(m); - String expected = "b(ILjava/lang/String;)Ljava/lang/String;"; - Assert.assertEquals(expected, actual); - } - - /** - * A method name is properly resolved, e.g.: - * `b` - */ - @Test - public void testMethodToMethodName() { - Method m = HierarchyHelper.B.class.getDeclaredMethods()[0]; - String actual = MethodSignatureUtil.methodName(m); - String expected = "b"; - Assert.assertEquals(expected, actual); - } - - /** - * A method descriptor is properly resolved, e.g.: - * `(ILjava/lang/String;)Ljava/lang/String;` - */ - @Test - public void testMethodToMethodDescriptor() { - Method m = HierarchyHelper.B.class.getDeclaredMethods()[0]; - String actual = MethodSignatureUtil.methodDescriptor(m); - String expected = "(ILjava/lang/String;)Ljava/lang/String;"; - Assert.assertEquals(expected, actual); - } - - /** - * Before: gr.gousiosg.javacg.stat.support.HierarchyHelper$B - * After: gr.gousiosg.javacg.stat.support.HierarchyHelper.B - */ - @Test - public void testSanitizeClassName() { - Class clazz = HierarchyHelper.B.class; - String name = MethodSignatureUtil.fullyQualifiedClassName(clazz); - Assert.assertFalse(name.contains("$")); - Assert.assertFalse(name.contains("/")); - } - - /** - * Before: edu/uic/cs398/Book/BookFactory - * After: edu.uic.cs398.Book.BookFactory - */ - @Test - public void testSanitizeJacocoClassName() { - String expected = "edu.uic.cs398.Book.BookFactory"; - String name = MethodSignatureUtil.sanitizeClassName(JACOCO_CLASS_NAME); - Assert.assertFalse(name.contains("$")); - Assert.assertFalse(name.contains("/")); - Assert.assertEquals(expected, name); - } - - /** - * The data from jacoco's XML report is converted properly - */ - @Test - public void testClassNameAndMethodNameAndMethodDescriptor() { - String actual = MethodSignatureUtil.fullyQualifiedMethodSignature(JACOCO_CLASS_NAME, JACOCO_METHOD_NAME, JACOCO_METHOD_DESCRIPTOR); - Assert.assertEquals(EXPECTED_JACOCO_CONVERSION, actual); - } - - /** - * The `.`-joined components of the combined string should match the expected full string - */ - @Test - public void testSplitAndJoin() { - Class clazz = HierarchyHelper.B.class; - Method method = clazz.getDeclaredMethods()[0]; - String namedMethodSignature = MethodSignatureUtil.namedMethodSignature(method); - String className = MethodSignatureUtil.fullyQualifiedClassName(clazz); - String combined = String.join(".", className, namedMethodSignature); - Assert.assertEquals(combined, MethodSignatureUtil.fullyQualifiedMethodSignature(clazz, method)); - } + private static final String JACOCO_CLASS_NAME = "edu/uic/cs398/Book/BookFactory"; + private static final String JACOCO_METHOD_NAME = "getBook"; + private static final String JACOCO_METHOD_DESCRIPTOR = "(I)Ledu/uic/cs398/Book/Book;"; + private static final String EXPECTED_JACOCO_CONVERSION = + "edu.uic.cs398.Book.BookFactory.getBook(I)Ledu/uic/cs398/Book/Book;"; + + /** + * These should all be equivalent + */ + @Test + public void testFullyQualifiedMethodNameEquivalence() { + // Get class and method + Class clazz = HierarchyHelper.B.class; + Method method = clazz.getDeclaredMethods()[0]; + String classname = MethodSignatureUtil.fullyQualifiedClassName(clazz); + String methodName = MethodSignatureUtil.methodName(method); + + // Get BCEL types + String signature = Type.getSignature(method); + Type[] methodArgumentTypes = Type.getArgumentTypes(signature); + Type methodReturnType = Type.getReturnType(signature); + + // test it once + String actual = + MethodSignatureUtil.fullyQualifiedMethodSignature( + classname, methodName, methodArgumentTypes, methodReturnType); + String expected = + "gr.gousiosg.javacg.stat.support.HierarchyHelper.B.b(ILjava/lang/String;)Ljava/lang/String;"; + Assert.assertEquals(expected, actual); + + // test against another variant + String another = MethodSignatureUtil.fullyQualifiedMethodSignature(clazz, method); + Assert.assertEquals(another, actual); + + another = + MethodSignatureUtil.fullyQualifiedMethodSignature( + classname, methodName, MethodSignatureUtil.methodDescriptor(method)); + Assert.assertEquals(another, actual); + + another = MethodSignatureUtil.fullyQualifiedMethodSignature(method); + Assert.assertEquals(another, actual); + } + + /** + * These should all be equivalent + */ + @Test + public void testMethodDescriptorEquivalence() { + // Get class and method + Class clazz = HierarchyHelper.B.class; + Method method = clazz.getDeclaredMethods()[0]; + String methodName = MethodSignatureUtil.methodName(method); + + // Get BCEL types + String signature = Type.getSignature(method); + Type[] methodArgumentTypes = Type.getArgumentTypes(signature); + Type methodReturnType = Type.getReturnType(signature); + + // regular and named descriptors + String regularDescriptor = "(ILjava/lang/String;)Ljava/lang/String;"; + String namedDescriptor = "b" + regularDescriptor; + + // test regular descriptors + String actual = MethodSignatureUtil.methodDescriptor(method); + Assert.assertEquals(regularDescriptor, actual); + + actual = MethodSignatureUtil.methodDescriptor(methodArgumentTypes, methodReturnType); + Assert.assertEquals(regularDescriptor, actual); + + // test named descriptors + actual = MethodSignatureUtil.namedMethodSignature(method); + Assert.assertEquals(namedDescriptor, actual); + + actual = + MethodSignatureUtil.namedMethodSignature(methodName, methodArgumentTypes, methodReturnType); + Assert.assertEquals(namedDescriptor, actual); + } + + /** + * A Class and Method is properly resolved to the fully qualified method signature, e.g.: + * `gr.gousiosg.javacg.stat.support.HierarchyHelper.B.b(ILjava/lang/String;)Ljava/lang/String;` + */ + @Test + public void testClassAndMethodToFullyQualifiedMethodSignature() { + Method m = HierarchyHelper.B.class.getDeclaredMethods()[0]; + String actual = MethodSignatureUtil.fullyQualifiedMethodSignature(HierarchyHelper.B.class, m); + String expected = + "gr.gousiosg.javacg.stat.support.HierarchyHelper.B.b(ILjava/lang/String;)Ljava/lang/String;"; + Assert.assertEquals(expected, actual); + } + + /** + * A method signature is properly resolved, e.g.: `b(ILjava/lang/String;)Ljava/lang/String;` + */ + @Test + public void testMethodToMethodSignature() { + Method m = HierarchyHelper.B.class.getDeclaredMethods()[0]; + String actual = MethodSignatureUtil.namedMethodSignature(m); + String expected = "b(ILjava/lang/String;)Ljava/lang/String;"; + Assert.assertEquals(expected, actual); + } + + /** + * A method name is properly resolved, e.g.: `b` + */ + @Test + public void testMethodToMethodName() { + Method m = HierarchyHelper.B.class.getDeclaredMethods()[0]; + String actual = MethodSignatureUtil.methodName(m); + String expected = "b"; + Assert.assertEquals(expected, actual); + } + + /** + * A method descriptor is properly resolved, e.g.: `(ILjava/lang/String;)Ljava/lang/String;` + */ + @Test + public void testMethodToMethodDescriptor() { + Method m = HierarchyHelper.B.class.getDeclaredMethods()[0]; + String actual = MethodSignatureUtil.methodDescriptor(m); + String expected = "(ILjava/lang/String;)Ljava/lang/String;"; + Assert.assertEquals(expected, actual); + } + + /** + * Before: gr.gousiosg.javacg.stat.support.HierarchyHelper$B After: + * gr.gousiosg.javacg.stat.support.HierarchyHelper.B + */ + @Test + public void testSanitizeClassName() { + Class clazz = HierarchyHelper.B.class; + String name = MethodSignatureUtil.fullyQualifiedClassName(clazz); + Assert.assertFalse(name.contains("$")); + Assert.assertFalse(name.contains("/")); + } + + /** + * Before: edu/uic/cs398/Book/BookFactory After: edu.uic.cs398.Book.BookFactory + */ + @Test + public void testSanitizeJacocoClassName() { + String expected = "edu.uic.cs398.Book.BookFactory"; + String name = MethodSignatureUtil.sanitizeClassName(JACOCO_CLASS_NAME); + Assert.assertFalse(name.contains("$")); + Assert.assertFalse(name.contains("/")); + Assert.assertEquals(expected, name); + } + + /** + * The data from jacoco's XML report is converted properly + */ + @Test + public void testClassNameAndMethodNameAndMethodDescriptor() { + String actual = + MethodSignatureUtil.fullyQualifiedMethodSignature( + JACOCO_CLASS_NAME, JACOCO_METHOD_NAME, JACOCO_METHOD_DESCRIPTOR); + Assert.assertEquals(EXPECTED_JACOCO_CONVERSION, actual); + } + + /** + * The `.`-joined components of the combined string should match the expected full string + */ + @Test + public void testSplitAndJoin() { + Class clazz = HierarchyHelper.B.class; + Method method = clazz.getDeclaredMethods()[0]; + String namedMethodSignature = MethodSignatureUtil.namedMethodSignature(method); + String className = MethodSignatureUtil.fullyQualifiedClassName(clazz); + String combined = String.join(".", className, namedMethodSignature); + Assert.assertEquals(combined, MethodSignatureUtil.fullyQualifiedMethodSignature(clazz, method)); + } } diff --git a/src/test/java/inttest/ConvexIT.java b/src/test/java/inttest/ConvexIT.java new file mode 100644 index 00000000..773373f4 --- /dev/null +++ b/src/test/java/inttest/ConvexIT.java @@ -0,0 +1,133 @@ +package inttest; + +import gr.gousiosg.javacg.stat.JCallGraph; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.Assert.assertTrue; + +public class ConvexIT { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConvexIT.class); + + private final String jarBaseOutput = Paths.get("artifacts", "output", "convex", "convex-core").toString(); + + private final Path convexJar = Paths.get(System.getProperty("user.dir"),jarBaseOutput, "convex-core-0.7.1.jar"); + private final Path convexDependencyJar = Paths.get(System.getProperty("user.dir"),jarBaseOutput, "convex-core-0.7.1-jar-with-dependencies.jar"); + private final Path convexTestJar = Paths.get(System.getProperty("user.dir"),jarBaseOutput, "convex-core-0.7.1-tests.jar"); + private final Path convexGraph = Paths.get(System.getProperty("user.dir"),"convex-core_graph"); + private final Path primitiveRoundTrip = Paths.get(System.getProperty("user.dir"),"output","GenTestFormat#primitiveRoundTrip.dot"); + private final Path primitiveRoundTripReachability = Paths.get(System.getProperty("user.dir"),"output","GenTestFormat#primitiveRoundTrip-reachability.dot"); + private final Path dataRoundTripFormat = Paths.get(System.getProperty("user.dir"),"output","GenTestFormat#dataRoundTrip.dot"); + private final Path dataRoundTripReachability = Paths.get(System.getProperty("user.dir"),"output","GenTestFormat#dataRoundTrip-reachability.dot"); + private final Path messageRoundTrip = Paths.get(System.getProperty("user.dir"),"output","GenTestFormat#messageRoundTrip.dot"); + private final Path messageRoundTripReachability = Paths.get(System.getProperty("user.dir"),"output","GenTestFormat#messageRoundTrip-reachability.dot"); + + @Before + public void setUp(){ + String outputDirectoryPath = System.getProperty("user.dir") + "/output/convex/"; + File outputDir = new File(outputDirectoryPath); + if(!outputDir.exists()) + outputDir.mkdir(); + } + + @Test + public void testA(){ + String [] args = {"git", "-c", "convex"}; + JCallGraph.main(args); + } + + @Test + public void testB(){ + String [] args = {"build", "-j", convexJar.toString(), + "-t", convexTestJar.toString(), "-o", "convex-core_graph"}; + JCallGraph.main(args); + } + + @Test + public void testC(){ + String [] args = {"test", "-c", "convex", "-f", "convex-core_graph"}; + JCallGraph.main(args); + } + + @Test + public void testD(){ + + // Git Stage + LOGGER.info("Starting Convex Git Verification"); + assertTrue(Files.exists(convexJar)); + assertTrue(Files.exists(convexDependencyJar)); + assertTrue(Files.exists(convexTestJar)); + + // Build Stage + LOGGER.info("Starting Convex Build Verification"); + assertTrue(Files.exists(convexGraph)); + + + // Test Stage + LOGGER.info("Starting Convex Test Verfication"); + assertTrue(Files.exists(primitiveRoundTrip)); + assertTrue(Files.exists(primitiveRoundTripReachability)); + assertTrue(Files.exists(dataRoundTripFormat)); + assertTrue(Files.exists(dataRoundTripReachability)); + assertTrue(Files.exists(messageRoundTrip)); + assertTrue(Files.exists(messageRoundTripReachability)); + + } + + // + // Create png files for comparison + @Test + public void testE() throws IOException, InterruptedException { + String cmd = "./buildpng.sh"; + String project = "convex"; + ProcessBuilder pb = new ProcessBuilder(cmd, project); + Process process = pb.start(); + BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while((line = br.readLine()) != null) + LOGGER.info(line); + process.waitFor(); + } + + // + // Test difference through diffimg + @Test + public void testF() throws IOException, InterruptedException { + String cmd = "./testdiff.sh"; + String project = "convex"; + ProcessBuilder pb = new ProcessBuilder(cmd, project); + Process process = pb.start(); + BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while((line = br.readLine()) != null) { + LOGGER.info(line); + if(line.contains("%")) { + String[] values = line.split(" : "); + Double percentDifference = Double.parseDouble(values[1].replace("%", "")); + Assert.assertTrue(percentDifference < 0.05); + } + } + process.waitFor(); + } + + @After + public void cleanUp() throws IOException, InterruptedException { + ProcessBuilder pb = new ProcessBuilder(); + pb.command("sh", "rm", "output/*.dot"); + Process process = pb.start(); + process.waitFor(); + } +} diff --git a/src/test/java/inttest/JFlexIT.java b/src/test/java/inttest/JFlexIT.java new file mode 100644 index 00000000..0f0fc679 --- /dev/null +++ b/src/test/java/inttest/JFlexIT.java @@ -0,0 +1,126 @@ +package inttest; + +import gr.gousiosg.javacg.stat.JCallGraph; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.Assert.assertTrue; + +public class JFlexIT { + private static final Logger LOGGER = LoggerFactory.getLogger(JFlexIT.class); + + private final String jarBaseOutput = Paths.get("artifacts", "output", "jflex", "jflex").toString(); + + private final Path jflexJar = Paths.get(System.getProperty("user.dir"),jarBaseOutput, "jflex-1.8.2.jar"); + private final Path jflexDependencyJar = Paths.get(System.getProperty("user.dir"),jarBaseOutput, "jflex-1.8.2-jar-with-dependencies.jar"); + private final Path jflexFullJar = Paths.get(System.getProperty("user.dir"),jarBaseOutput, "jflex-full-1.8.2.jar"); + private final Path jflexTestJar = Paths.get(System.getProperty("user.dir"),jarBaseOutput, "jflex-1.8.2-tests.jar"); + private final Path jflexGraph = Paths.get(System.getProperty("user.dir"),"jflex_graph"); + private final Path removeAdd = Paths.get(System.getProperty("user.dir"), "output", "StateSetQuickcheck#removeAdd-reachability.dot"); + private final Path addStateDoesNotRemove = Paths.get(System.getProperty("user.dir"), "output", "StateSetQuickcheck#addStateDoesNotRemove-reachability.dot"); + private final Path containsElements = Paths.get(System.getProperty("user.dir"), "output", "StateSetQuickcheck#containsElements-reachability.dot"); + private final Path addSingle = Paths.get(System.getProperty("user.dir"), "output", "CharClassesQuickcheck#addSingle-reachability.dot"); + private final Path addSingleSingleton = Paths.get(System.getProperty("user.dir"), "output", "CharClassesQuickcheck#addSingleSingleton-reachability.dot"); + private final Path addSet = Paths.get(System.getProperty("user.dir"), "output", "CharClassesQuickcheck#addSet-reachability.dot"); + private final Path addString = Paths.get(System.getProperty("user.dir"), "output", "CharClassesQuickcheck#addString-reachability.dot"); + + @Before + public void setUp(){ + String outputDirectoryPath = System.getProperty("user.dir") + "/output/jflex/"; + File outputDir = new File(outputDirectoryPath); + if(!outputDir.exists()) + outputDir.mkdir(); + } + + @Test + public void testA(){ + String [] args = {"git", "-c", "jflex"}; + JCallGraph.main(args); + } + + @Test + public void testB(){ + String [] args = {"build", "-j", jflexJar.toString(), + "-t", jflexTestJar.toString(), "-o", "jflex_graph"}; + JCallGraph.main(args); + } + + @Test + public void testC(){ + String [] args = {"test", "-c", "jflex", "-f", "jflex_graph"}; + JCallGraph.main(args); + } + + @Test + public void testD(){ + // Git Stage + LOGGER.info("Starting JFlex Git Verification"); + assertTrue(Files.exists(jflexJar)); + assertTrue(Files.exists(jflexDependencyJar)); + assertTrue(Files.exists(jflexFullJar)); + assertTrue(Files.exists(jflexTestJar)); + + // Build Stage + LOGGER.info("Starting JFlex Build Verification"); + assertTrue(Files.exists(jflexGraph)); + + // Test Stage + LOGGER.info("Starting JFlex Test Verification"); + assertTrue(Files.exists(removeAdd)); + assertTrue(Files.exists(addStateDoesNotRemove)); + assertTrue(Files.exists(containsElements)); + assertTrue(Files.exists(addSingle)); + assertTrue(Files.exists(addSingleSingleton)); + assertTrue(Files.exists(addSet)); + assertTrue(Files.exists(addString)); + + } + + // + // Create png files for comparison + @Test + public void testE() throws IOException, InterruptedException { + String cmd = "./buildpng.sh"; + String project = "jflex"; + ProcessBuilder pb = new ProcessBuilder(cmd, project); + Process process = pb.start(); + BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while((line = br.readLine()) != null) + LOGGER.info(line); + process.waitFor(); + } + + + // + // Test difference through diffimg + @Test + public void testF() throws IOException, InterruptedException { + String cmd = "./testdiff.sh"; + String project = "jflex"; + ProcessBuilder pb = new ProcessBuilder(cmd, project); + Process process = pb.start(); + BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while((line = br.readLine()) != null) { + LOGGER.info(line); + if(line.contains("%")) { + String[] values = line.split(" : "); + Double percentDifference = Double.parseDouble(values[1].replace("%", "")); + Assert.assertTrue(percentDifference < 0.05); + } + } + process.waitFor(); + } +} diff --git a/src/test/java/inttest/MphTableIT.java b/src/test/java/inttest/MphTableIT.java new file mode 100644 index 00000000..27893c8f --- /dev/null +++ b/src/test/java/inttest/MphTableIT.java @@ -0,0 +1,145 @@ +package inttest; + +import gr.gousiosg.javacg.stat.JCallGraph; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import static org.junit.Assert.assertTrue; + +public class MphTableIT { + + private static final Logger LOGGER = LoggerFactory.getLogger(MphTableIT.class); + private final Path mphJar = Paths.get(System.getProperty("user.dir"),"artifacts","output","mph-table", "mph-table-1.0.6-SNAPSHOT.jar"); + private final Path mphTestJar = Paths.get(System.getProperty("user.dir"),"artifacts","output","mph-table","mph-table-1.0.6-SNAPSHOT-tests.jar"); + private final Path mphGraph = Paths.get(System.getProperty("user.dir"),"mph-table_graph"); + private final Path mphSmartByteSerializer = Paths.get(System.getProperty("user.dir"),"output","TestSmartByteSerializer#canRoundTripBytes.dot"); + private final Path mphSmartByteSerializerReachability = Paths.get(System.getProperty("user.dir"),"output","TestSmartByteSerializer#canRoundTripBytes-reachability.dot"); + private final Path mphSmartIntegerSerializer = Paths.get(System.getProperty("user.dir"),"output","TestSmartIntegerSerializer#canRoundTripIntegers.dot"); + private final Path mphSmartIntegerSerializerReachability = Paths.get(System.getProperty("user.dir"),"output","TestSmartIntegerSerializer#canRoundTripIntegers-reachability.dot"); + private final Path mphSmartListSerializer = Paths.get(System.getProperty("user.dir"),"output","TestSmartListSerializer#canRoundTripSerializableLists.dot"); + private final Path mphSmartListSerializerReachability = Paths.get(System.getProperty("user.dir"),"output","TestSmartListSerializer#canRoundTripSerializableLists-reachability.dot"); + private final Path mphSmartLongSerializer = Paths.get(System.getProperty("user.dir"),"output","TestSmartLongSerializer#canRoundTripLongs.dot"); + private final Path mphSmartLongSerializerReachability = Paths.get(System.getProperty("user.dir"),"output","TestSmartLongSerializer#canRoundTripLongs-reachability.dot"); + private final Path mphSmartPairSerializer = Paths.get(System.getProperty("user.dir"),"output","TestSmartPairSerializer#canRoundTripPairs.dot"); + private final Path mphSmartPairSerializerReachability = Paths.get(System.getProperty("user.dir"),"output","TestSmartPairSerializer#canRoundTripPairs-reachability.dot"); + private final Path mphSmartShortSerializer = Paths.get(System.getProperty("user.dir"),"output","TestSmartShortSerializer#canRoundTripShort.dot"); + private final Path mphSmartShortSerializerReachability = Paths.get(System.getProperty("user.dir"),"output","TestSmartShortSerializer#canRoundTripShort-reachability.dot"); + private final Path mphSmartStringSerializer = Paths.get(System.getProperty("user.dir"),"output","TestSmartStringSerializer#canRoundTripStrings.dot"); + private final Path mphSmartStringSerializerReachability = Paths.get(System.getProperty("user.dir"),"output","TestSmartStringSerializer#canRoundTripStrings-reachability.dot"); + + @Before + public void setUp(){ + String outputDirectoryPath = System.getProperty("user.dir") + "/output/mph-table/"; + File outputDir = new File(outputDirectoryPath); + if(!outputDir.exists()) + outputDir.mkdir(); + } + + + // Git Stage + @Test + public void testA(){ + String [] args = {"git", "-c", "mph-table"}; + JCallGraph.main(args); + } + //Build Stage + @Test + public void testB(){ + String [] args = {"build", "-j", "./artifacts/output/mph-table/mph-table-1.0.6-SNAPSHOT.jar", + "-t", "./artifacts/output/mph-table/mph-table-1.0.6-SNAPSHOT-tests.jar", "-o", "mph-table_graph"}; + JCallGraph.main(args); + } + + // Test Stage + @Test + public void testC(){ + String [] args = {"test", "-c", "mph-table", "-f", "mph-table_graph"}; + JCallGraph.main(args); + } + + // Validation Checks + @Test + public void testD(){ + // Git Stage + LOGGER.info("Starting Mph-Table Git Verification"); + assertTrue(Files.exists(mphJar)); + assertTrue(Files.exists(mphTestJar)); + + + // Build Stage + LOGGER.info("Starting Mph-Table Build Verification"); + assertTrue(Files.exists(mphGraph)); + + // Test Stage + assertTrue(Files.exists(mphSmartByteSerializer)); + assertTrue(Files.exists(mphSmartByteSerializerReachability)); + assertTrue(Files.exists(mphSmartIntegerSerializer)); + assertTrue(Files.exists(mphSmartIntegerSerializerReachability)); + assertTrue(Files.exists(mphSmartListSerializer)); + assertTrue(Files.exists(mphSmartListSerializerReachability)); + assertTrue(Files.exists(mphSmartLongSerializer)); + assertTrue(Files.exists(mphSmartLongSerializerReachability)); + assertTrue(Files.exists(mphSmartPairSerializer)); + assertTrue(Files.exists(mphSmartPairSerializerReachability)); + assertTrue(Files.exists(mphSmartShortSerializer)); + assertTrue(Files.exists(mphSmartShortSerializerReachability)); + assertTrue(Files.exists(mphSmartStringSerializer)); + assertTrue(Files.exists(mphSmartStringSerializerReachability)); + } + + // + // Create png files for comparison + @Test + public void testE() throws IOException, InterruptedException { + String cmd = "./buildpng.sh"; + String project = "mph-table"; + ProcessBuilder pb = new ProcessBuilder(cmd, project); + Process process = pb.start(); + BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while((line = br.readLine()) != null) + LOGGER.info(line); + process.waitFor(); + } + + // + // Test difference through diffimg + @Test + public void testF() throws IOException, InterruptedException { + String cmd = "./testdiff.sh"; + String project = "mph-table"; + ProcessBuilder pb = new ProcessBuilder(cmd, project); + Process process = pb.start(); + BufferedReader br = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + while((line = br.readLine()) != null) { + LOGGER.info(line); + if(line.contains("%")) { + String[] values = line.split(" : "); + Double percentDifference = Double.parseDouble(values[1].replace("%", "")); + Assert.assertTrue(percentDifference < 0.05); + } + } + process.waitFor(); + } + + @After + public void cleanUp() throws IOException, InterruptedException { + ProcessBuilder pb = new ProcessBuilder(); + pb.command("sh", "rm", "output/*.dot"); + Process process = pb.start(); + process.waitFor(); + } +} diff --git a/testdiff.sh b/testdiff.sh new file mode 100755 index 00000000..8a36f887 --- /dev/null +++ b/testdiff.sh @@ -0,0 +1,8 @@ +for i in `ls output/"$1"/*-reachability.png`; +do + echo Processing Difference "$i"... + image=${i:7} + python3 configuredDiffImg.py artifacts/expected/"$image" "$i" + echo Done +done +echo Completed diffimg testing \ No newline at end of file