Skip to content

Commit

Permalink
- changes stack matcher to match against the right order instead of e…
Browse files Browse the repository at this point in the history
…xact indices

- changes test cases to better test the new stack matcher
- adds a new maven module for scala instrumentation testing
- adds docs for scala and improves runseq docs
  • Loading branch information
arminbalalaie committed Apr 3, 2019
1 parent 5523ada commit 26825a6
Show file tree
Hide file tree
Showing 12 changed files with 329 additions and 28 deletions.
19 changes: 13 additions & 6 deletions docs/pages/deterministic.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Available internal events are:
* **Scheduling Event**: This event can be of type *BLOCKING* or *UNBLOCKING* and can happen before or after a specific
stack trace. The stack trace should come from a stack trace event definition. When defining this kind of events, the
definition should be a pair of blocking and unblocking events. Basically, make sure to finally unblock everything that
has been blocked. This event is useful when it is needed to block all of the threads for a specific stack trace, do some
has been blocked. This event is useful when it is needed to block all the threads for a specific stack trace, do some
other stuff or let the other threads make progress, and then, unblock the blocked threads.

.. code-block:: java
Expand All @@ -38,9 +38,16 @@ Available internal events are:
.and()
* **Stack Trace Event**: This event is kind of like a scheduling event except that nothing happens between blocking and
unblocking. All of the threads with the defined stack trace will be blocked until the dependencies of the event are
unblocking. All the threads with the defined stack trace will be blocked until the dependencies of the event are
satisfied (based on the defined run sequence). The blocking can happen before or after a method. This event can act as
an indicator that the program has reached a specific method with a specific stack trace.
an indicator that the program has reached a specific method with a specific stack trace. To specify the stack traces,
the default is to have a list of method signatures with ``[package].[class].[method]`` where the last called method comes
at the end. As some languages may not have the concept of class or package, you may want to check :doc:`runseq` as well
for additional instructions for specific languages.

It is important to note that, the method signatures are not required to be present exactly in the given indices in the
current stack trace. Only the right order of appearance is sufficient.


.. code-block:: java
Expand All @@ -64,7 +71,7 @@ Available internal events are:
Test Case Events
================

Test case events are the connection point between the test case and the |projectName|'s runtime engine. Internal events's
Test case events are the connection point between the test case and the |projectName|'s runtime engine. Internal events'
orders are enforced by the runtime engine, but it is the test case responsibility to enforce the test case events if they
are included in the run sequence.

Expand All @@ -83,7 +90,7 @@ respectively.
.. code-block:: java
new Deployment.Builder("sample")
.runSequence("bast1 * w1 * ubast1 * (gc1 | x1)")
.runSequence("bast1 * tc1 * ubast1 * (gc1 | x1)")
This run sequence blocks all the threads in node ``n1`` with the stack trace of event ``st1`` (``bast1``), waits for the
test case to enforce ``tc1``, unblcoks the blocked threads in node ``n1`` (``ubast1``), and finally, in parallel, performs
Expand All @@ -97,7 +104,7 @@ injecting a failure.
runner.runtime().enforceOrder("tc1", 10, () -> runner.runtime().clockDrift("n1", -100));
Here, when the dependencies of event ``tc1`` are satisified, a clock drift in the amount -100ms will be applied to node
Here, when the dependencies of event ``tc1`` are satisified, a clock drift in the amount of -100ms will be applied to node
``n1``, and ``tc1`` event will be marked as satisfied. If after 10 seconds the dependencies of ``tc1`` are not satisfied,
a ``TimeoutException`` will be thrown. If the only thing that the test case needs is to wait for an event or its
dependencies to be satisfied the ``waitFor`` method can be used.
Expand Down
10 changes: 5 additions & 5 deletions docs/pages/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,11 @@ So, we create a Dockerfile in the ``docker/Dockerfile`` address with the followi
.. important::

In case you are using Docker Toolbox (and consequently boot2docker) on Windows or Mac, be aware that the customized
Tiny Core Linux created for the boot2docker misses ``sched_netem`` kernel module which is included in most of the
linux distributions and is needed for ``tc`` command in the ``iproute`` package to work. So, unless you manually add
that kernel module to the installed virtual machine by Docker Toolbox, you won't be able to use the network operation
capabilities of |projectName|.
In case you are using Docker Toolbox (and consequently boot2docker) on Windows or Mac, be aware that your currently
installed boot2docker image may be missing ``sched_netem`` kernel module which is included in most of the
linux distributions and is needed for ``tc`` command in the ``iproute`` package to work. So, unless you upgrade your
boot2docker image (normally through running ``docker-machine upgrade [machine_name]``, you won't be able to use the
network operation capabilities of |projectName|.

Adding a Test Case
==================
Expand Down
10 changes: 5 additions & 5 deletions docs/pages/runseq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ instructions as this may differ based on the programming language in use.
Java
====

`AspectJ <https://www.eclipse.org/aspectj/>`_ is used for Java instrumentation. AspectJ 1.8+ should work perfectly with
You need to choose ``ServiceType.JAVA`` as your service type. `AspectJ <https://www.eclipse.org/aspectj/>`_ is used for Java instrumentation. AspectJ 1.8+ should work perfectly with
|projectName|. You need to install Aspectj on your machine and expose ``ASPECTJ_HOME`` environment variable pointing to the
home directory of AspectJ in your machine. Also, you need to include AspectJ and |projectName| runtime dependencies to your
project. Example dependencies to be added to your pom file with AspectJ 1.8.12 are as follows:
Expand All @@ -37,8 +37,8 @@ project. Example dependencies to be added to your pom file with AspectJ 1.8.12 a
Finally, you need to mark:

* all of the required jar files or class file directories to run your application as **library path**.
* all of the jar files or class file directories which contain a method included as the last method in one of the stack
* all the required jar files or class file directories to run your application as **library path**.
* all the jar files or class file directories which contain a method included as the last method in one of the stack
trace events as **instrumentable path**

.. code-block:: java
Expand All @@ -57,8 +57,8 @@ Finally, you need to mark:
Scala
=====

The requirements for Scala is the same as Java as again `AspectJ <https://www.eclipse.org/aspectj/>`_ is used for the
instrumentation. You also need to choose ``ServiceType.SCALA`` as your service type. There is only a subtle point when
You need to choose ``ServiceType.SCALA`` as your service type. The requirements for Scala is the same as Java as again
`AspectJ <https://www.eclipse.org/aspectj/>`_ is used for the instrumentation. There is only a subtle point when
specifying the stack traces with Scala. When it is intended to instrument a Scala ``object``, you need to add a trailing
``$`` to the name of the object. This is because internally when such a code compiles to JVM bytecodes, a new class with
trailing ``$`` will be created and the original class will proxy calls to itself to that class. However, if internal methods
Expand Down
16 changes: 10 additions & 6 deletions failifyrt/src/main/java/io/failify/rt/StackMatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
* This class is responsible for matching a given stack trace against the current stack trace
*/
public class StackMatcher {
// TODO change this so it returns true if the stack traces exist in the right order

/**
* This method matches the given stack arg against the current stack trace
Expand All @@ -46,13 +45,18 @@ public boolean match(String stack) {
List<String> inputList = Arrays.asList(inputTraces);
Collections.reverse(inputList);
inputTraces = inputList.toArray(new String[inputList.size()]);
for (int i=0; i<inputTraces.length; i++) {
// +4 is needed to get rid of method signatures to call this method
if (!getFullTraceString(elements[i+4]).equals(inputTraces[i].trim())) {
return false;

int curIndexToMatch = 0;
// i=2 is needed to get rid of getStackTrace and match method signatures
for (int i=2; i<elements.length; i++) {
if (getFullTraceString(elements[i]).equals(inputTraces[curIndexToMatch].trim())) {
curIndexToMatch++;
if (curIndexToMatch == inputTraces.length) {
return true;
}
}
}
return true;
return false;
}

/**
Expand Down
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
<module>failifyrt</module>
<module>failify</module>
<module>sample-multithread</module>
<module>sample-scala</module>
</modules>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,21 @@ public void start() {
}
}

public void helloWorld(String msg) {
hello(msg);
}

public void hello(String msg) {
System.out.println(msg);
}

public void helloWorld1() {
try {
Thread.sleep(new Random().nextInt(50));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Hello World 1!");
helloWorld("Hello World 1!");
System.out.println(new Date());
}

Expand All @@ -81,7 +89,7 @@ public void helloWorld2() {
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Hello World 2!");
helloWorld("Hello World 2!");
}

public void helloWorld3() {
Expand All @@ -90,7 +98,7 @@ public void helloWorld3() {
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Hello World 3!");
helloWorld("Hello World 3!");
System.out.println(new Date());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,12 @@ public void simpleDefinition() throws DeploymentVerificationException, RuntimeEn
.serviceType(ServiceType.JAVA).and()
// Node Definitions
.withNode("n1", "s1")
.stackTrace("e1", "io.failify.samples.multithread.Main.helloWorld1")
.stackTrace("e2", "io.failify.samples.multithread.Main.helloWorld2")
.stackTrace("e3", "io.failify.samples.multithread.Main.helloWorld3")
.stackTrace("e1", "io.failify.samples.multithread.Main.helloWorld1," +
"io.failify.samples.multithread.Main.hello")
.stackTrace("e2", "io.failify.samples.multithread.Main.helloWorld2," +
"io.failify.samples.multithread.Main.helloWorld")
.stackTrace("e3", "io.failify.samples.multithread.Main.helloWorld3," +
"io.failify.samples.multithread.Main.hello")
.stackTrace("e4", "org.apache.commons.io.FilenameUtils.normalize")
.blockBefore("bbe2", "e2")
.unblockBefore("ubbe2", "e2")
Expand Down
25 changes: 25 additions & 0 deletions sample-scala/docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#MIT License
#
#Copyright (c) 2017 Armin Balalaie
#
#Permission is hereby granted, free of charge, to any person obtaining a copy
#of this software and associated documentation files (the "Software"), to deal
#in the Software without restriction, including without limitation the rights
#to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
#copies of the Software, and to permit persons to whom the Software is
#furnished to do so, subject to the following conditions:
#
#The above copyright notice and this permission notice shall be included in all
#copies or substantial portions of the Software.
#
#THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
#IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
#FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
#AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
#LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
#OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
#SOFTWARE.

FROM debian:stretch

RUN apt update && apt install -y iptables scala
90 changes: 90 additions & 0 deletions sample-scala/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.failify</groupId>
<artifactId>failify-parent</artifactId>
<version>0.2.0-SNAPSHOT</version>
</parent>

<groupId>io.failify.samples</groupId>
<artifactId>sample-scala</artifactId>
<name>Failify Sample - Scala</name>
<description>A simple undeterministic multi thread application in Scala to test Failify functionality</description>
<url>https://github.com/failify/failify</url>


<properties>
<encoding>UTF-8</encoding>
<scala.version>2.12.8</scala.version>
<scala.compat.version>2.12</scala.compat.version>
</properties>

<dependencies>
<dependency>
<groupId>org.scala-lang</groupId>
<artifactId>scala-library</artifactId>
<version>${scala.version}</version>
</dependency>
<dependency>
<groupId>io.failify</groupId>
<artifactId>failify</artifactId>
<version>${project.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.failify</groupId>
<artifactId>failifyrt</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.3.RC1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
<scope>test</scope>
</dependency>
<!-- Test -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<sourceDirectory>src/main/scala</sourceDirectory>
<testSourceDirectory>src/test/scala</testSourceDirectory>
<plugins>
<plugin>
<groupId>net.alchim31.maven</groupId>
<artifactId>scala-maven-plugin</artifactId>
<version>3.3.2</version>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>testCompile</goal>
</goals>
<configuration>
<args>
<arg>-dependencyfile</arg>
<arg>${project.build.directory}/.scala_dependencies</arg>
</args>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

0 comments on commit 26825a6

Please sign in to comment.