├── .gitignore ├── .travis.yml ├── LICENSE.md ├── README.md ├── pom.xml └── src ├── main └── java │ └── uk │ └── co │ └── probablyfine │ └── bytemonkey │ ├── AddChanceOfFailure.java │ ├── AgentArguments.java │ ├── ByteMonkeyAgent.java │ ├── ByteMonkeyClassTransformer.java │ ├── ByteMonkeyException.java │ ├── CreateAndThrowException.java │ ├── DirectlyThrowException.java │ ├── FilterByClassAndMethodName.java │ ├── OperationMode.java │ └── testfiles │ ├── FaultTestObject.java │ ├── MissingPropertyException.java │ ├── NullabilityTestPojo.java │ └── TryCatchTestObject.java └── test └── java └── uk └── co └── probablyfine └── bytemonkey ├── fault ├── DefaultExceptionTypeTest.java ├── Rate0Test.java ├── Rate100Test.java ├── Rate50Test.java └── ThrowExceptionIfDeclaredTest.java ├── latency └── Rate100Test.java ├── nullify ├── DoNothingWithEmptyParamMethodsTest.java ├── DoNothingWithNoObjectParamMethodsTest.java ├── NullifyArgumentsTest.java └── OnlyNullifyNonPrimitiveArgumentsTest.java └── shortcircuit ├── TryCatchObjectNormalTest.java ├── TryCatchObjectSCTest.java └── TryCatchObjectSCWithParamTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | target/ 4 | .classpath 5 | .project 6 | .settings/ 7 | bin 8 | spooned/ 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 4 | - oraclejdk9 5 | - oraclejdk11 6 | after_script: 7 | - mvn clean test jacoco:report coveralls:report -Dcoveralls.token=$coveralls_repo_token 8 | 9 | matrix: 10 | allow_failures: 11 | - jdk: oraclejdk9 12 | - jdk: oraclejdk11 13 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Alex Wilson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # byte-monkey 2 | 3 | [![Build Status](https://travis-ci.org/mrwilson/byte-monkey.svg?branch=master)](https://travis-ci.org/mrwilson/byte-monkey) 4 | [![Coverage Status](https://coveralls.io/repos/github/mrwilson/byte-monkey/badge.svg?branch=master)](https://coveralls.io/github/mrwilson/byte-monkey?branch=master) 5 | 6 | Byte-Monkey is a small Java library for testing failure scenarios in JVM applications - it works by instrumenting application code on the fly to deliberately introduce faults like exceptions and latency. Original blogpost [here](http://blog.probablyfine.co.uk/2016/05/30/announcing-byte-monkey.html). 7 | 8 | 9 | ## Download 10 | 11 | Latest version: [1.0.0](https://github.com/mrwilson/byte-monkey/releases/download/1.0.0/byte-monkey.jar) 12 | 13 | ## How to use 14 | 15 | ```bash 16 | java -javaagent:byte-monkey.jar -jar your-java-app.jar 17 | ``` 18 | 19 | ## Supported Modes 20 | 21 | * **Fault**: Throw exceptions from methods that declare those exceptions 22 | * **Latency**: Introduce latency on method-calls 23 | * **Nullify**: Replace the first non-primitive argument to the method with *null* 24 | * **Short-circuit**: Throw corresponding exceptions at the very beginning of try blocks 25 | 26 | ## Options 27 | 28 | * `mode`: What mode to run in - currently supports `fault`, `latency`, `nullify`, and `scircuit`. **Default is fault** 29 | * `rate`: Value between 0 and 1 - how often to activate the fault. **Default is 1, i.e. 100%** 30 | * `filter`: Only instrument packages or methods matching the (java-style) regex. **Default is .*, i.e. all methods** 31 | 32 | byte-monkey is configured with a comma-separated key-value pair string of the options as the agent argument. 33 | 34 | ```bash 35 | java -javaagent:byte-monkey.jar=mode:fault,rate:0.5,filter:uk/co/probablyfine/ -jar your-java-app.jar 36 | ``` 37 | 38 | The example above would run in fault mode, activating on 50% of eligible method calls, for anything in the package tree below `uk.co.probablyfine` 39 | 40 | ## Modes 41 | 42 | ### Fault 43 | 44 | Running byte-monkey in `fault` mode will cause the first declared exception in a method signature to be thrown. 45 | 46 | **CAVEAT**: Byte-Monkey can only create Exceptions that expose a public default constructor as a result of how it instantiates them. If such a constructor doesn't exist, it falls back to a `ByteMonkeyException` instead. 47 | 48 | ### Latency 49 | 50 | Running byte-monkey in `latency` mode will cause the method to sleep before executing further instructions. 51 | 52 | There is a configuration option available only during this mode: 53 | 54 | * `latency`: Duration (in millis) to wait on method calls, only valid when running in **Latency** mode. **Default is 100ms** 55 | 56 | Example: `java -javaagent:byte-monkey.jar=mode:latency,rate:0.5,latency:150 -jar your-java-app.jar` 57 | 58 | ### Nullify 59 | 60 | Running byte-monkey in `nullify` mode will replace the first non-primitive argument to the method call with a null value. 61 | 62 | Methods with only primitive arguments or no arguments at all will not be affected by the agent in this mode. 63 | 64 | ### Short-circuit 65 | 66 | Running byte-monkey in `scircuit` mode will throw corresponding exceptions in the very beginning of try blocks. 67 | 68 | There is a configuration option available only during this mode: 69 | 70 | * `tcindex`: Index of which exception to throw when there are multiple catch blocks, e.g. `tcindex=0` indicates the first type of exception in the catch block. Only valid when running in **Short-circuit** mode. **Default is -1/first** 71 | 72 | Example: 73 | ```bash 74 | java -javaagent:byte-monkey.jar=mode:scircuit,filter:package/path/ClassName/MethodName,tcindex=0 -jar your-java-app.jar 75 | ``` 76 | 77 | You can read [this paper](https://hal.inria.fr/hal-01062969/document) or [this blog](http://blog.gluckzhang.com/archives/107/) for more information about short-circuit testing. 78 | 79 | ## Implementation Details 80 | 81 | Byte-Monkey uses the JVM [Instrumentation API](https://docs.oracle.com/javase/8/docs/api/java/lang/instrument/package-summary.html). Implementing the API enables you to register transformers that can iterate over (and transform) class files as they are loaded by the JVM. Byte-Monkey uses [Objectweb ASM](http://asm.ow2.org/) which comes packaged with the JDK to chance the underlying bytecode of loaded classes 82 | 83 | ### Injecting Failure 84 | 85 | The bytecode of a simple "Hello, World!" method prior to having an exception injected looks like this: 86 | 87 | ``` 88 | public void printSomething() throws java.io.IOException; 89 | Code: 90 | 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 91 | 3: ldc #3 // String Hello! 92 | 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 93 | 8: return 94 | ``` 95 | 96 | After being transformed by the Byte-Monkey, it instead looks like this: 97 | ``` 98 | public void printSomething() throws java.io.IOException; 99 | Code: 100 | 0: ldc2_w #18 // double 0.5d 101 | 3: invokestatic #25 // Method uk/co/probablyfine/bytemonkey/AddChanceOfFailure.shouldActivate:(D)Z 102 | 6: ifeq 15 103 | 9: ldc #26 // String java/io/IOException 104 | 11: invokestatic #32 // Method uk/co/probablyfine/bytemonkey/CreateAndThrowException.throwOrDefault:(Ljava/lang/String;)Ljava/lang/Throwable; 105 | 14: athrow 106 | 15: getstatic #38 // Field java/lang/System.out:Ljava/io/PrintStream; 107 | 18: ldc #40 // String Hello! 108 | 20: invokevirtual #46 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 109 | 23: return 110 | ``` 111 | 112 | This is the core of how Byte-Monkey works: 113 | 114 | * **0:** Load the failure injection rate onto the stack 115 | * **3:** A call to `AddChanceOfFailure.shouldActivate` which returns true/false depending on the rate 116 | * **6:** If `shouldActivate` was false, we jump straight to instruction 15 - the beginning of the original code. 117 | * **9:** Load the name of the exception that would be thrown (here an IOException) 118 | * **11:** Create the exception if it has a default constructor, or create a wrapper exception 119 | * **14:** Throw the exception 120 | 121 | For modes other than `fault`, instructions 9 to 14 are replaced with mode-specific instructions. -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | uk.co.probablyfine 6 | byte-monkey 7 | 1.0.0 8 | 9 | 10 | 1.8 11 | 1.8 12 | UTF-8 13 | 1.3 14 | NOT-A-TOKEN 15 | 16 | 17 | 18 | ${project.artifactId} 19 | 20 | 21 | 22 | org.apache.maven.plugins 23 | maven-jar-plugin 24 | 3.0.0 25 | 26 | 27 | true 28 | 29 | uk.co.probablyfine.bytemonkey.ByteMonkeyAgent 30 | 31 | 32 | 33 | 34 | 35 | org.apache.maven.plugins 36 | maven-surefire-plugin 37 | 2.19.1 38 | 39 | false 40 | 41 | 42 | 43 | -noverify 44 | 45 | 46 | 47 | org.apache.maven.plugins 48 | maven-compiler-plugin 49 | 2.5.1 50 | 51 | -XDignore.symbol.file 52 | 1.8 53 | 1.8 54 | 55 | 56 | 57 | org.jacoco 58 | jacoco-maven-plugin 59 | 0.7.9 60 | 61 | 62 | prepare-agent 63 | 64 | prepare-agent 65 | 66 | 67 | 68 | 69 | 70 | org.eluder.coveralls 71 | coveralls-maven-plugin 72 | 4.3.0 73 | 74 | ${coveralls.token} 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | junit 83 | junit 84 | 4.12 85 | test 86 | 87 | 88 | com.ea.agentloader 89 | ea-agent-loader 90 | 1.0.0 91 | test 92 | 93 | 94 | org.hamcrest 95 | hamcrest-core 96 | ${hamcrest.version} 97 | test 98 | 99 | 100 | org.hamcrest 101 | hamcrest-library 102 | ${hamcrest.version} 103 | test 104 | 105 | 106 | org.hamcrest 107 | hamcrest-all 108 | ${hamcrest.version} 109 | test 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /src/main/java/uk/co/probablyfine/bytemonkey/AddChanceOfFailure.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey; 2 | 3 | import java.util.Random; 4 | 5 | import jdk.internal.org.objectweb.asm.Opcodes; 6 | import jdk.internal.org.objectweb.asm.tree.FrameNode; 7 | import jdk.internal.org.objectweb.asm.tree.InsnList; 8 | import jdk.internal.org.objectweb.asm.tree.JumpInsnNode; 9 | import jdk.internal.org.objectweb.asm.tree.LabelNode; 10 | import jdk.internal.org.objectweb.asm.tree.LdcInsnNode; 11 | import jdk.internal.org.objectweb.asm.tree.MethodInsnNode; 12 | 13 | public class AddChanceOfFailure { 14 | 15 | private static final Random random = new Random(); 16 | 17 | public InsnList apply(InsnList newInstructions, double chanceOfFailure) { 18 | final InsnList list = new InsnList(); 19 | 20 | final LabelNode originalCodeLabel = new LabelNode(); 21 | 22 | list.add(new LdcInsnNode(chanceOfFailure)); 23 | list.add(new MethodInsnNode( 24 | Opcodes.INVOKESTATIC, 25 | "uk/co/probablyfine/bytemonkey/AddChanceOfFailure", 26 | "shouldActivate", 27 | "(D)Z", 28 | false // this is not a method on an interface 29 | )); 30 | 31 | list.add(new JumpInsnNode(Opcodes.IFEQ, originalCodeLabel)); 32 | 33 | list.add(newInstructions); 34 | 35 | list.add(new FrameNode( 36 | Opcodes.F_APPEND, // append to the last stack frame 37 | 0, new Object[] {}, // no local variables here 38 | 0, new Object[] {} // no stack either! 39 | )); 40 | 41 | list.add(originalCodeLabel); 42 | 43 | return list; 44 | } 45 | 46 | public static boolean shouldActivate(double chanceOfFailure) { 47 | return random.nextDouble() < chanceOfFailure; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/uk/co/probablyfine/bytemonkey/AgentArguments.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey; 2 | 3 | public class AgentArguments { 4 | private final long latency; 5 | private final double chanceOfFailure; 6 | private final int tcIndex; 7 | 8 | public AgentArguments(long latency, double activationRatio, int tcIndex) { 9 | this.latency = latency; 10 | this.chanceOfFailure = activationRatio; 11 | this.tcIndex = tcIndex; 12 | } 13 | 14 | public long latency() { 15 | return latency; 16 | } 17 | 18 | public double chanceOfFailure() { 19 | return chanceOfFailure; 20 | } 21 | 22 | public int tcIndex() { return tcIndex; } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/uk/co/probablyfine/bytemonkey/ByteMonkeyAgent.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey; 2 | 3 | import java.lang.instrument.Instrumentation; 4 | import java.lang.instrument.UnmodifiableClassException; 5 | 6 | public class ByteMonkeyAgent { 7 | 8 | public static void premain(String agentArguments, Instrumentation instrumentation) throws UnmodifiableClassException { 9 | ByteMonkeyClassTransformer transformer = new ByteMonkeyClassTransformer(agentArguments); 10 | instrumentation.addTransformer(transformer); 11 | } 12 | 13 | /* Duplicate of premain(), needed for ea-agent-loader in tests */ 14 | public static void agentmain(String agentArguments, Instrumentation instrumentation) throws UnmodifiableClassException { 15 | premain(agentArguments, instrumentation); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/uk/co/probablyfine/bytemonkey/ByteMonkeyClassTransformer.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey; 2 | 3 | import jdk.internal.org.objectweb.asm.ClassReader; 4 | import jdk.internal.org.objectweb.asm.ClassWriter; 5 | import jdk.internal.org.objectweb.asm.tree.*; 6 | 7 | import java.lang.instrument.ClassFileTransformer; 8 | import java.lang.instrument.IllegalClassFormatException; 9 | import java.security.ProtectionDomain; 10 | import java.util.Arrays; 11 | import java.util.Map; 12 | import java.util.Optional; 13 | import java.util.stream.Collectors; 14 | 15 | import static java.util.Optional.ofNullable; 16 | 17 | public class ByteMonkeyClassTransformer implements ClassFileTransformer { 18 | 19 | private final AddChanceOfFailure addChanceOfFailure = new AddChanceOfFailure(); 20 | 21 | private final OperationMode failureMode; 22 | private final AgentArguments arguments; 23 | private final FilterByClassAndMethodName filter; 24 | 25 | public ByteMonkeyClassTransformer(String args) { 26 | Map configuration = argumentMap(args == null ? "" : args); 27 | 28 | long latency = Long.valueOf(configuration.getOrDefault("latency","100")); 29 | double activationRatio = Double.valueOf(configuration.getOrDefault("rate","1")); 30 | int tcIndex = Integer.valueOf(configuration.getOrDefault("tcindex", "-1")); 31 | 32 | this.arguments = new AgentArguments(latency, activationRatio, tcIndex); 33 | this.failureMode = OperationMode.fromLowerCase(configuration.getOrDefault("mode", OperationMode.FAULT.name())); 34 | this.filter = new FilterByClassAndMethodName(configuration.getOrDefault("filter", ".*")); 35 | } 36 | 37 | private Map argumentMap(String args) { 38 | return Arrays 39 | .stream(args.split(",")) 40 | .map(line -> line.split(":")) 41 | .filter(line -> line.length == 2) 42 | .collect(Collectors.toMap( 43 | keyValue -> keyValue[0], 44 | keyValue -> keyValue[1]) 45 | ); 46 | } 47 | 48 | public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, 49 | ProtectionDomain protectionDomain, byte[] classFileBuffer 50 | ) throws IllegalClassFormatException { 51 | return meddle(classFileBuffer); 52 | } 53 | 54 | private byte[] meddle(byte[] classFileBuffer) { 55 | ClassNode cn = new ClassNode(); 56 | new ClassReader(classFileBuffer).accept(cn, 0); 57 | 58 | if (cn.name.startsWith("java/") || cn.name.startsWith("sun/") || cn.name.contains("$")) return classFileBuffer; 59 | 60 | switch (failureMode) { 61 | case SCIRCUIT: 62 | int tcIndex = arguments.tcIndex(); 63 | if (tcIndex < 0) { 64 | cn.methods.stream() 65 | .filter(method -> !method.name.startsWith("<")) 66 | .filter(method -> filter.matches(cn.name, method.name)) 67 | .filter(method -> method.tryCatchBlocks.size() > 0) 68 | .forEach(method -> { 69 | // inject an exception in each try-catch block 70 | // take the first exception type in catch block 71 | // for 1 try -> n catch, we should do different injections through params 72 | // TODO: these codes really need to be beautified 73 | LabelNode ln = method.tryCatchBlocks.get(0).start; 74 | int i = 0; 75 | for (TryCatchBlockNode tc : method.tryCatchBlocks) { 76 | if (ln == tc.start && i > 0) { 77 | // if two try-catch-block-nodes have the same "start", it indicates that it's one try block with multiple catch 78 | // so we should only inject one exception each time 79 | continue; 80 | } 81 | InsnList newInstructions = failureMode.generateByteCode(tc, tcIndex, arguments); 82 | method.maxStack += newInstructions.size(); 83 | method.instructions.insert(tc.start, newInstructions); 84 | ln = tc.start; 85 | i++; 86 | } 87 | }); 88 | } else { 89 | // should work together with filter 90 | cn.methods.stream() 91 | .filter(method -> !method.name.startsWith("<")) 92 | .filter(method -> filter.matches(cn.name, method.name)) 93 | .filter(method -> method.tryCatchBlocks.size() > 0) 94 | .forEach(method -> { 95 | int index = 0; 96 | for (TryCatchBlockNode tc : method.tryCatchBlocks) { 97 | if (index == tcIndex) { 98 | InsnList newInstructions = failureMode.generateByteCode(tc, tcIndex, arguments); 99 | method.maxStack += newInstructions.size(); 100 | method.instructions.insert(tc.start, newInstructions); 101 | break; 102 | } else { 103 | index ++; 104 | } 105 | } 106 | }); 107 | } 108 | break; 109 | default: 110 | cn.methods.stream() 111 | .filter(method -> !method.name.startsWith("<")) 112 | .filter(method -> filter.matches(cn.name, method.name)) 113 | .forEach(method -> { 114 | createNewInstructions(method).ifPresent(newInstructions -> { 115 | method.maxStack += newInstructions.size(); 116 | method.instructions.insertBefore( 117 | method.instructions.getFirst(), 118 | newInstructions 119 | ); 120 | }); 121 | }); 122 | break; 123 | } 124 | 125 | final ClassWriter cw = new ClassWriter(0); 126 | cn.accept(cw); 127 | return cw.toByteArray(); 128 | } 129 | 130 | private Optional createNewInstructions(MethodNode method) { 131 | InsnList newInstructions = failureMode.generateByteCode(method, arguments); 132 | 133 | return ofNullable( 134 | addChanceOfFailure.apply(newInstructions, arguments.chanceOfFailure()) 135 | ); 136 | } 137 | 138 | } -------------------------------------------------------------------------------- /src/main/java/uk/co/probablyfine/bytemonkey/ByteMonkeyException.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey; 2 | 3 | public class ByteMonkeyException extends RuntimeException { 4 | public ByteMonkeyException(String exceptionName) { 5 | super("You've made a monkey out of me! Simulating throw of ["+exceptionName+"]"); 6 | } 7 | } -------------------------------------------------------------------------------- /src/main/java/uk/co/probablyfine/bytemonkey/CreateAndThrowException.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey; 2 | 3 | public class CreateAndThrowException { 4 | 5 | public static void throwDirectly(String name) throws Throwable { 6 | String dotSeparatedClassName = name.replace("/", "."); 7 | Class p = Class.forName(dotSeparatedClassName, false, ClassLoader.getSystemClassLoader()); 8 | if (Throwable.class.isAssignableFrom(p)) { 9 | throw (Throwable) p.newInstance(); 10 | } else { 11 | throw new ByteMonkeyException(name); 12 | } 13 | } 14 | 15 | public static Throwable throwOrDefault(String name) { 16 | String dotSeparatedClassName = name.replace("/", "."); 17 | 18 | try { 19 | Class p = Class.forName(dotSeparatedClassName, false, ClassLoader.getSystemClassLoader()); 20 | 21 | if (Throwable.class.isAssignableFrom(p)) { 22 | return (Throwable) p.newInstance(); 23 | } else { 24 | return new ByteMonkeyException(name); 25 | } 26 | } catch (IllegalAccessException e) { 27 | return new ByteMonkeyException(name); 28 | } catch (Exception e) { 29 | return new RuntimeException(name); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /src/main/java/uk/co/probablyfine/bytemonkey/DirectlyThrowException.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey; 2 | 3 | public class DirectlyThrowException { 4 | public static void throwDirectly(String name) throws Throwable { 5 | String dotSeparatedClassName = name.replace("/", "."); 6 | Class p = Class.forName(dotSeparatedClassName, false, ClassLoader.getSystemClassLoader()); 7 | if (Throwable.class.isAssignableFrom(p)) { 8 | throw (Throwable) p.newInstance(); 9 | } else { 10 | throw new ByteMonkeyException(name); 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/uk/co/probablyfine/bytemonkey/FilterByClassAndMethodName.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | public class FilterByClassAndMethodName { 6 | 7 | private final Pattern pattern; 8 | 9 | public FilterByClassAndMethodName(String regex) { 10 | this.pattern = Pattern.compile(regex); 11 | } 12 | 13 | public boolean matches(String className, String methodName) { 14 | String fullName = className + "/" + methodName; 15 | 16 | return this.pattern.matcher(fullName).find(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/uk/co/probablyfine/bytemonkey/OperationMode.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey; 2 | 3 | import jdk.internal.org.objectweb.asm.Opcodes; 4 | import jdk.internal.org.objectweb.asm.Type; 5 | import jdk.internal.org.objectweb.asm.tree.*; 6 | 7 | import java.util.List; 8 | import java.util.OptionalInt; 9 | import java.util.stream.IntStream; 10 | 11 | public enum OperationMode { 12 | SCIRCUIT { 13 | public InsnList generateByteCode(TryCatchBlockNode tryCatchBlock, int tcIndex, AgentArguments arguments) { 14 | InsnList list = new InsnList(); 15 | 16 | list.add(new LdcInsnNode(tryCatchBlock.type)); 17 | list.add(new MethodInsnNode( 18 | Opcodes.INVOKESTATIC, 19 | "uk/co/probablyfine/bytemonkey/DirectlyThrowException", 20 | "throwDirectly", 21 | "(Ljava/lang/String;)V", 22 | false // this is not a method on an interface 23 | )); 24 | 25 | return list; 26 | } 27 | 28 | @Override 29 | public InsnList generateByteCode(MethodNode method, AgentArguments arguments) { 30 | // won't use this method 31 | return null; 32 | } 33 | }, 34 | LATENCY { 35 | @Override 36 | public InsnList generateByteCode(MethodNode method, AgentArguments arguments) { 37 | final InsnList list = new InsnList(); 38 | 39 | list.add(new LdcInsnNode(arguments.latency())); 40 | list.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "java/lang/Thread", "sleep", "(J)V", false)); 41 | 42 | return list; 43 | } 44 | @Override 45 | public InsnList generateByteCode(TryCatchBlockNode tryCatchBlock, int tcIndex, AgentArguments arguments) { 46 | // won't use this method 47 | return null; 48 | } 49 | }, 50 | FAULT { 51 | @Override 52 | public InsnList generateByteCode(MethodNode method, AgentArguments arguments) { 53 | final List exceptionsThrown = method.exceptions; 54 | 55 | InsnList list = new InsnList(); 56 | 57 | if (exceptionsThrown.size() == 0) return list; 58 | 59 | list.add(new LdcInsnNode(exceptionsThrown.get(0))); 60 | list.add(new MethodInsnNode( 61 | Opcodes.INVOKESTATIC, 62 | "uk/co/probablyfine/bytemonkey/CreateAndThrowException", 63 | "throwOrDefault", 64 | "(Ljava/lang/String;)Ljava/lang/Throwable;", 65 | false // this is not a method on an interface 66 | )); 67 | 68 | list.add(new InsnNode(Opcodes.ATHROW)); 69 | 70 | return list; 71 | } 72 | @Override 73 | public InsnList generateByteCode(TryCatchBlockNode tryCatchBlock, int tcIndex, AgentArguments arguments) { 74 | // won't use this method 75 | return null; 76 | } 77 | }, 78 | NULLIFY { 79 | @Override 80 | public InsnList generateByteCode(MethodNode method, AgentArguments arguments) { 81 | final InsnList list = new InsnList(); 82 | 83 | final Type[] argumentTypes = Type.getArgumentTypes(method.desc); 84 | 85 | final OptionalInt firstNonPrimitiveArgument = IntStream 86 | .range(0, argumentTypes.length) 87 | .filter(i -> argumentTypes[i].getSort() == Type.OBJECT) 88 | .findFirst(); 89 | 90 | if (!firstNonPrimitiveArgument.isPresent()) return list; 91 | 92 | list.add(new InsnNode(Opcodes.ACONST_NULL)); 93 | list.add(new VarInsnNode(Opcodes.ASTORE, firstNonPrimitiveArgument.getAsInt() + 1)); 94 | 95 | return list; 96 | } 97 | @Override 98 | public InsnList generateByteCode(TryCatchBlockNode tryCatchBlock, int tcIndex, AgentArguments arguments) { 99 | // won't use this method 100 | return null; 101 | } 102 | }; 103 | 104 | public static OperationMode fromLowerCase(String mode) { 105 | return OperationMode.valueOf(mode.toUpperCase()); 106 | } 107 | 108 | public abstract InsnList generateByteCode(MethodNode method, AgentArguments arguments); 109 | public abstract InsnList generateByteCode(TryCatchBlockNode tryCatchBlock, int tcIndex, AgentArguments arguments); 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/uk/co/probablyfine/bytemonkey/testfiles/FaultTestObject.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey.testfiles; 2 | 3 | import java.io.IOException; 4 | 5 | public class FaultTestObject { 6 | public void printSomething() throws IOException { 7 | System.out.println("Hello!"); 8 | } 9 | 10 | public void printSomethingElse() throws IllegalStateException { 11 | System.out.println("Goodbye!"); 12 | } 13 | 14 | public void printAndThrowNonPublicException() throws ExceptionWithNoPublicConstructor { 15 | System.out.println("Uh-oh!"); 16 | } 17 | 18 | public void safePrint() { 19 | System.out.println("Hi!"); 20 | } 21 | 22 | public static class ExceptionWithNoPublicConstructor extends RuntimeException { 23 | private ExceptionWithNoPublicConstructor() {} // No constructor for you! 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /src/main/java/uk/co/probablyfine/bytemonkey/testfiles/MissingPropertyException.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey.testfiles; 2 | 3 | public class MissingPropertyException extends Exception { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/uk/co/probablyfine/bytemonkey/testfiles/NullabilityTestPojo.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey.testfiles; 2 | 3 | public class NullabilityTestPojo { 4 | 5 | private String name; 6 | 7 | public NullabilityTestPojo(String name) { 8 | this.name = name; 9 | } 10 | 11 | public String getName() { 12 | return name; 13 | } 14 | 15 | public void setName1stArg(String name) { 16 | this.name = name; 17 | } 18 | 19 | public void setName2ndArg(int i, String name) { 20 | this.name = name; 21 | } 22 | 23 | public void setNameNoArgs() { 24 | this.name = "zap"; 25 | } 26 | 27 | public void setNamePrimitiveArgs(int i, int i2) { 28 | this.name = "zoom"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/uk/co/probablyfine/bytemonkey/testfiles/TryCatchTestObject.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey.testfiles; 2 | 3 | import java.io.IOException; 4 | import java.util.Random; 5 | 6 | public class TryCatchTestObject { 7 | public String multipleTryCatch() { 8 | StringBuilder result = new StringBuilder(); 9 | try { 10 | // 1st try-catch block 11 | result.append("_1st line in 1st tc"); 12 | String arg = getArgument(); 13 | String key = format(arg); 14 | } catch (MissingPropertyException e) { 15 | // MissingPropertyException occured in sourceIndependentTryCatch 16 | result.append("_mpe in 1st tc"); 17 | } catch (IOException e) { 18 | result.append("_ioe in 1st tc"); 19 | } 20 | 21 | try { 22 | // 2nd try-catch block 23 | result.append("_1st line in 2nd tc"); 24 | String arg = getArgument(); 25 | String key = format(arg); 26 | } catch (MissingPropertyException e) { 27 | result.append("_mpe in 2nd tc"); 28 | } catch (IOException e) { 29 | result.append("_ioe in 2nd tc"); 30 | } 31 | 32 | return result.toString(); 33 | } 34 | 35 | public String sourceIndependentTryCatch() { 36 | Boolean isCacheActivated = false; 37 | System.out.println("in try right away!!"); 38 | try { 39 | System.out.println("first line in try!!"); 40 | String arg = getArgument(); 41 | String key = format(arg); 42 | return getProperty(key, isCacheActivated); 43 | } catch (MissingPropertyException e) { 44 | System.out.println("MissingPropertyException occured in sourceIndependentTryCatch"); 45 | return "missing property"; 46 | } catch (IOException e) { 47 | System.out.println("IOException occured in sourceIndependentTryCatch"); 48 | return "get argument failed"; 49 | } 50 | } 51 | 52 | public String sourceDependentTryCatch() { 53 | Boolean isCacheActivated = true; 54 | String arg = "str_arg"; 55 | String key = "str_key"; 56 | try { 57 | isCacheActivated = getCacheAvailability(); 58 | return getProperty(key, isCacheActivated); 59 | } catch (MissingPropertyException e) { 60 | System.out.println("MissingPropertyException occured in sourceDependentTryCatch"); 61 | if (isCacheActivated) { 62 | return "missing property"; 63 | } else { 64 | throw new CacheDisableException(); 65 | } 66 | } 67 | } 68 | 69 | public String purelyResilientTryCatch() { 70 | String key = "str_key"; 71 | try { 72 | return getPropertyFromCache(key); 73 | } catch (MissingPropertyException e) { 74 | System.out.println("MissingPropertyException occured in purelyResilientTryCatch"); 75 | return getPropertyFromFile(key); 76 | } 77 | } 78 | 79 | private String getProperty(String key, Boolean isCacheActivated) throws MissingPropertyException { 80 | return null; 81 | } 82 | 83 | private String format(String arg) throws MissingPropertyException { 84 | return null; 85 | } 86 | 87 | private String getArgument() throws IOException { 88 | return null; 89 | } 90 | 91 | private boolean getCacheAvailability() { 92 | Random random = new Random(); 93 | return random.nextDouble() < 0.5; 94 | } 95 | 96 | private String getPropertyFromFile(String key) { 97 | return "property_from_file"; 98 | } 99 | 100 | private String getPropertyFromCache(String key) throws MissingPropertyException { 101 | return "property_from_cache"; 102 | } 103 | 104 | public static class CacheDisableException extends RuntimeException { 105 | } 106 | 107 | public static void main(String[] args) { 108 | TryCatchTestObject tcTest = new TryCatchTestObject(); 109 | 110 | System.out.println(tcTest.multipleTryCatch()); 111 | tcTest.sourceIndependentTryCatch(); 112 | tcTest.sourceDependentTryCatch(); 113 | tcTest.purelyResilientTryCatch(); 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/test/java/uk/co/probablyfine/bytemonkey/fault/DefaultExceptionTypeTest.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey.fault; 2 | 3 | import com.ea.agentloader.AgentLoader; 4 | import org.hamcrest.core.StringContains; 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | import org.junit.rules.ExpectedException; 8 | import uk.co.probablyfine.bytemonkey.ByteMonkeyAgent; 9 | import uk.co.probablyfine.bytemonkey.ByteMonkeyException; 10 | import uk.co.probablyfine.bytemonkey.testfiles.FaultTestObject; 11 | 12 | import java.io.IOException; 13 | 14 | import static org.hamcrest.core.StringContains.containsString; 15 | 16 | public class DefaultExceptionTypeTest { 17 | 18 | @Rule 19 | public ExpectedException expectedException = ExpectedException.none(); 20 | 21 | @Test 22 | public void shouldThrowSomethingIDK() throws IOException { 23 | AgentLoader.loadAgentClass( 24 | ByteMonkeyAgent.class.getName(), 25 | "mode:fault,filter:uk/co/probablyfine/bytemonkey/testfiles/FaultTestObject/printAndThrowNonPublicException" 26 | ); 27 | 28 | expectedException.expect(ByteMonkeyException.class); 29 | expectedException.expectMessage(containsString("FaultTestObject$ExceptionWithNoPublicConstructor")); 30 | 31 | new FaultTestObject().printAndThrowNonPublicException(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/test/java/uk/co/probablyfine/bytemonkey/fault/Rate0Test.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey.fault; 2 | 3 | import com.ea.agentloader.AgentLoader; 4 | import org.junit.Rule; 5 | import org.junit.Test; 6 | import org.junit.rules.ExpectedException; 7 | import uk.co.probablyfine.bytemonkey.ByteMonkeyAgent; 8 | import uk.co.probablyfine.bytemonkey.testfiles.FaultTestObject; 9 | 10 | import java.io.IOException; 11 | 12 | public class Rate0Test { 13 | 14 | @Rule 15 | public ExpectedException expectedException = ExpectedException.none(); 16 | 17 | @Test 18 | public void shouldThrowNotExceptionWhenInstrumented_throwPercentageIs0() throws IOException { 19 | AgentLoader.loadAgentClass(ByteMonkeyAgent.class.getName(), "mode:fault,rate:0"); 20 | 21 | new FaultTestObject().printSomething(); 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /src/test/java/uk/co/probablyfine/bytemonkey/fault/Rate100Test.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey.fault; 2 | 3 | import com.ea.agentloader.AgentLoader; 4 | import org.junit.Rule; 5 | import org.junit.Test; 6 | import org.junit.rules.ExpectedException; 7 | import uk.co.probablyfine.bytemonkey.ByteMonkeyAgent; 8 | import uk.co.probablyfine.bytemonkey.testfiles.FaultTestObject; 9 | 10 | import java.io.IOException; 11 | 12 | public class Rate100Test { 13 | 14 | @Rule 15 | public ExpectedException expectedException = ExpectedException.none(); 16 | 17 | @Test 18 | public void shouldThrowExceptionWhenInstrumented_throwPercentageIs100() throws IOException { 19 | AgentLoader.loadAgentClass(ByteMonkeyAgent.class.getName(), "mode:fault,rate:1"); 20 | 21 | expectedException.expect(IOException.class); 22 | 23 | new FaultTestObject().printSomething(); 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /src/test/java/uk/co/probablyfine/bytemonkey/fault/Rate50Test.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey.fault; 2 | 3 | import com.ea.agentloader.AgentLoader; 4 | import org.junit.Rule; 5 | import org.junit.Test; 6 | import org.junit.rules.ExpectedException; 7 | import uk.co.probablyfine.bytemonkey.ByteMonkeyAgent; 8 | import uk.co.probablyfine.bytemonkey.testfiles.FaultTestObject; 9 | 10 | import java.io.IOException; 11 | import java.util.concurrent.atomic.AtomicInteger; 12 | import java.util.stream.IntStream; 13 | 14 | import static org.hamcrest.MatcherAssert.assertThat; 15 | import static org.hamcrest.core.Is.is; 16 | import static org.hamcrest.number.IsCloseTo.closeTo; 17 | 18 | public class Rate50Test { 19 | 20 | @Rule 21 | public ExpectedException expectedException = ExpectedException.none(); 22 | 23 | @Test 24 | public void shouldThrowExceptionWhenInstrumented_throwPercentageIsRoughlyHalf() throws IOException { 25 | AgentLoader.loadAgentClass(ByteMonkeyAgent.class.getName(), "mode:fault,rate:0.5"); 26 | 27 | final AtomicInteger counter = new AtomicInteger(0); 28 | 29 | IntStream.range(0, 10_000).forEach(x -> { 30 | try { 31 | new FaultTestObject().printSomething(); 32 | } catch (Exception e) { 33 | if (IOException.class.isAssignableFrom(e.getClass())) { 34 | counter.incrementAndGet(); 35 | } 36 | } 37 | }); 38 | 39 | double percentFailure = counter.get()/10_000f; 40 | 41 | assertThat(percentFailure, is(closeTo(0.5f, 0.1))); 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /src/test/java/uk/co/probablyfine/bytemonkey/fault/ThrowExceptionIfDeclaredTest.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey.fault; 2 | 3 | import com.ea.agentloader.AgentLoader; 4 | import org.junit.Rule; 5 | import org.junit.Test; 6 | import org.junit.rules.ExpectedException; 7 | import uk.co.probablyfine.bytemonkey.ByteMonkeyAgent; 8 | import uk.co.probablyfine.bytemonkey.testfiles.FaultTestObject; 9 | 10 | import java.io.IOException; 11 | 12 | public class ThrowExceptionIfDeclaredTest { 13 | 14 | @Rule 15 | public ExpectedException expectedException = ExpectedException.none(); 16 | 17 | @Test 18 | public void shouldThrowCorrectException() throws IOException { 19 | AgentLoader.loadAgentClass( 20 | ByteMonkeyAgent.class.getName(), 21 | "mode:fault,filter:uk/co/probablyfine/bytemonkey/testfiles/FaultTestObject/printSomethingElse" 22 | ); 23 | 24 | expectedException.expect(IllegalStateException.class); 25 | 26 | new FaultTestObject().printSomething(); 27 | new FaultTestObject().printSomethingElse(); 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /src/test/java/uk/co/probablyfine/bytemonkey/latency/Rate100Test.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey.latency; 2 | 3 | import com.ea.agentloader.AgentLoader; 4 | import org.junit.Test; 5 | import uk.co.probablyfine.bytemonkey.ByteMonkeyAgent; 6 | import uk.co.probablyfine.bytemonkey.testfiles.FaultTestObject; 7 | 8 | import java.io.IOException; 9 | 10 | import static org.junit.Assert.assertTrue; 11 | 12 | public class Rate100Test { 13 | 14 | @Test 15 | public void shouldAddLatency() throws IOException { 16 | AgentLoader.loadAgentClass( 17 | ByteMonkeyAgent.class.getName(), 18 | "mode:latency,rate:1,latency:200,filter:uk/co/probablyfine/bytemonkey/testfiles" 19 | ); 20 | 21 | long timeTaken = timed(new FaultTestObject()::safePrint); 22 | 23 | assertTrue("Actually took "+timeTaken+"ms", timeTaken >= 200 && timeTaken < 500); 24 | } 25 | 26 | public static long timed(Runnable runnable) { 27 | long startTime = System.currentTimeMillis(); 28 | 29 | runnable.run(); 30 | 31 | return System.currentTimeMillis() - startTime; 32 | } 33 | 34 | } -------------------------------------------------------------------------------- /src/test/java/uk/co/probablyfine/bytemonkey/nullify/DoNothingWithEmptyParamMethodsTest.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey.nullify; 2 | 3 | import com.ea.agentloader.AgentLoader; 4 | import org.junit.Test; 5 | import uk.co.probablyfine.bytemonkey.ByteMonkeyAgent; 6 | import uk.co.probablyfine.bytemonkey.testfiles.NullabilityTestPojo; 7 | 8 | import java.io.IOException; 9 | 10 | import static org.hamcrest.CoreMatchers.is; 11 | import static org.junit.Assert.assertThat; 12 | 13 | public class DoNothingWithEmptyParamMethodsTest { 14 | 15 | @Test 16 | public void shouldOnlyNullifyObjects() throws IOException { 17 | AgentLoader.loadAgentClass( 18 | ByteMonkeyAgent.class.getName(), 19 | "mode:nullify,rate:1,filter:uk/co/probablyfine/bytemonkey/testfiles/NullabilityTestPojo/setNameNoArgs" 20 | ); 21 | 22 | NullabilityTestPojo pojo = new NullabilityTestPojo("foo"); 23 | 24 | pojo.setNameNoArgs(); 25 | 26 | assertThat(pojo.getName(), is("zap")); 27 | } 28 | } -------------------------------------------------------------------------------- /src/test/java/uk/co/probablyfine/bytemonkey/nullify/DoNothingWithNoObjectParamMethodsTest.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey.nullify; 2 | 3 | import com.ea.agentloader.AgentLoader; 4 | import org.junit.Test; 5 | import uk.co.probablyfine.bytemonkey.ByteMonkeyAgent; 6 | import uk.co.probablyfine.bytemonkey.testfiles.NullabilityTestPojo; 7 | 8 | import java.io.IOException; 9 | 10 | import static org.hamcrest.CoreMatchers.is; 11 | import static org.junit.Assert.assertThat; 12 | 13 | public class DoNothingWithNoObjectParamMethodsTest { 14 | 15 | @Test 16 | public void shouldOnlyNullifyObjects() throws IOException { 17 | AgentLoader.loadAgentClass( 18 | ByteMonkeyAgent.class.getName(), 19 | "mode:nullify,rate:1,filter:uk/co/probablyfine/bytemonkey/testfiles/NullabilityTestPojo/setNamePrimitiveArgs" 20 | ); 21 | 22 | NullabilityTestPojo pojo = new NullabilityTestPojo("foo"); 23 | 24 | pojo.setNamePrimitiveArgs(1,2); 25 | 26 | assertThat(pojo.getName(), is("zoom")); 27 | } 28 | } -------------------------------------------------------------------------------- /src/test/java/uk/co/probablyfine/bytemonkey/nullify/NullifyArgumentsTest.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey.nullify; 2 | 3 | import com.ea.agentloader.AgentLoader; 4 | import org.junit.Test; 5 | import uk.co.probablyfine.bytemonkey.ByteMonkeyAgent; 6 | import uk.co.probablyfine.bytemonkey.testfiles.NullabilityTestPojo; 7 | 8 | import java.io.IOException; 9 | 10 | import static org.junit.Assert.assertNull; 11 | 12 | public class NullifyArgumentsTest { 13 | 14 | @Test 15 | public void shouldNullifyArguments() throws IOException { 16 | AgentLoader.loadAgentClass( 17 | ByteMonkeyAgent.class.getName(), 18 | "mode:nullify,rate:1,filter:uk/co/probablyfine/bytemonkey/testfiles/NullabilityTestPojo/setName1stArg" 19 | ); 20 | 21 | NullabilityTestPojo pojo = new NullabilityTestPojo("foo"); 22 | 23 | pojo.setName1stArg("bar"); 24 | 25 | assertNull(pojo.getName()); 26 | 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /src/test/java/uk/co/probablyfine/bytemonkey/nullify/OnlyNullifyNonPrimitiveArgumentsTest.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey.nullify; 2 | 3 | import com.ea.agentloader.AgentLoader; 4 | import org.junit.Test; 5 | import uk.co.probablyfine.bytemonkey.ByteMonkeyAgent; 6 | import uk.co.probablyfine.bytemonkey.testfiles.NullabilityTestPojo; 7 | 8 | import java.io.IOException; 9 | 10 | import static org.junit.Assert.assertNull; 11 | 12 | public class OnlyNullifyNonPrimitiveArgumentsTest { 13 | 14 | @Test 15 | public void shouldOnlyNullifyObjects() throws IOException { 16 | AgentLoader.loadAgentClass( 17 | ByteMonkeyAgent.class.getName(), 18 | "mode:nullify,rate:1,filter:uk/co/probablyfine/bytemonkey/testfiles/NullabilityTestPojo/setName2ndArg" 19 | ); 20 | 21 | NullabilityTestPojo pojo = new NullabilityTestPojo("foo"); 22 | 23 | pojo.setName2ndArg(1, "bar"); 24 | 25 | assertNull(pojo.getName()); 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /src/test/java/uk/co/probablyfine/bytemonkey/shortcircuit/TryCatchObjectNormalTest.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey.shortcircuit; 2 | 3 | import org.junit.Assert; 4 | import org.junit.Test; 5 | import uk.co.probablyfine.bytemonkey.testfiles.TryCatchTestObject; 6 | 7 | public class TryCatchObjectNormalTest { 8 | @Test 9 | public void normalMultipleTryCatchTest() { 10 | // do not inject exceptions, multipleTryCatch() should execute smoothly 11 | TryCatchTestObject tcTest = new TryCatchTestObject(); 12 | Assert.assertEquals(tcTest.multipleTryCatch(), "_1st line in 1st tc_1st line in 2nd tc"); 13 | } 14 | } -------------------------------------------------------------------------------- /src/test/java/uk/co/probablyfine/bytemonkey/shortcircuit/TryCatchObjectSCTest.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey.shortcircuit; 2 | 3 | import com.ea.agentloader.AgentLoader; 4 | import org.junit.Assert; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import uk.co.probablyfine.bytemonkey.ByteMonkeyAgent; 8 | import uk.co.probablyfine.bytemonkey.testfiles.TryCatchTestObject; 9 | 10 | public class TryCatchObjectSCTest { 11 | 12 | @Before 13 | public void loadAgent() { 14 | AgentLoader.loadAgentClass(ByteMonkeyAgent.class.getName(), "mode:scircuit"); 15 | } 16 | 17 | @Test 18 | public void scMultipleTryCatchTest() { 19 | // this time, we do short-circuit testing, exceptions will be injected into the beginning of every try block 20 | // hence "_1st line in xxx tc" should not appear in the return value 21 | TryCatchTestObject tcTest = new TryCatchTestObject(); 22 | Assert.assertEquals(tcTest.multipleTryCatch(), "_mpe in 1st tc_mpe in 2nd tc"); 23 | } 24 | 25 | public static void main(String[] args) { 26 | AgentLoader.loadAgentClass(ByteMonkeyAgent.class.getName(), "mode:scircuit"); 27 | 28 | TryCatchTestObject tcTest = new TryCatchTestObject(); 29 | System.out.println(tcTest.multipleTryCatch()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/uk/co/probablyfine/bytemonkey/shortcircuit/TryCatchObjectSCWithParamTest.java: -------------------------------------------------------------------------------- 1 | package uk.co.probablyfine.bytemonkey.shortcircuit; 2 | 3 | import com.ea.agentloader.AgentLoader; 4 | import org.junit.Assert; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import uk.co.probablyfine.bytemonkey.ByteMonkeyAgent; 8 | import uk.co.probablyfine.bytemonkey.testfiles.TryCatchTestObject; 9 | 10 | public class TryCatchObjectSCWithParamTest { 11 | @Before 12 | public void loadAgent() { 13 | AgentLoader.loadAgentClass(ByteMonkeyAgent.class.getName(), "mode:scircuit,tcindex:2,filter:uk/co/probablyfine/bytemonkey/testfiles/TryCatchTestObject/multipleTryCatch"); 14 | } 15 | 16 | @Test 17 | public void scMultipleTryCatchWithParamTest() { 18 | TryCatchTestObject tcTest = new TryCatchTestObject(); 19 | Assert.assertEquals("_1st line in 1st tc_mpe in 2nd tc", tcTest.multipleTryCatch()); 20 | } 21 | } --------------------------------------------------------------------------------