Announcement Announcement Module
Collapse
No announcement yet.
System clock for QA team Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • System clock for QA team

    My QA team is doing business lifecycle testing (i.e. aging, expiring, due, past due etc) , that requires application clock to be moved. I can change all my code to refer to a adjusted clock (that I control). The issues is the (web) applications uses several 3rd party tools (e.g. Spring Batch, Activiti etc.) that relies on current time and uses System.currentTimeMillis() directly or indirectly through Date or Calendar.

    Option 1 - Spring AOP. When I tried this option it seemed it only instruments Spring loaded beans only (?) Since System class was loaded outside of Spring framework it could not instrument it.

    Option 2 - JMockit. Somewhat unconventional to have JMockit jar past JUnit.

    Option 3 - Use Java 6 instrumentation (common piece between Option 1 and Option 2). Back to the basics... (find the relevant code below).

    However, the assert in the test code always fails.

    I have hit a roadblock with all the three options. Can't believe no one have done this before, but can't find an reasonable solution either.


    Code:
    public class InstrumentationAgent {
        private static Instrumentation instrumentation = null;
    
    
        /**
         * JVM hook to dynamically load InstrumentationAgent at runtime.
         * 
         * The agent class may have an agentmain method for use when the agent is
         * started after VM startup.
         * 
         * @param agentArgument
         * @param instrumentation
         */
        public static void agentmain(String agentArgument, Instrumentation instrumentation) {
            InstrumentationAgent.instrumentation = instrumentation;
        }
    
        /**
         * Programmatic hook to dynamically load modified byte codes. This method initializes/load the agent if necessary.
         * 
         * @param definitions
         * @throws Exception
         */
        public static void redefineClasses(ClassDefinition... definitions) throws Exception {
            if (InstrumentationAgent.instrumentation == null) {
                loadAgent();
            }
    
            InstrumentationAgent.instrumentation.redefineClasses(definitions);
        }
    
        private synchronized static void loadAgent() throws Exception {
            if (InstrumentationAgent.instrumentation != null) {
                return;
            }
    
            // Build the agent.jar file
            final File jarFile = File.createTempFile("agent", ".jar");
            jarFile.deleteOnExit();
    
            final Manifest manifest = new Manifest();
            final Attributes mainAttributes = manifest.getMainAttributes();
            mainAttributes.put(Attributes.Name.MANIFEST_VERSION, "1.0");
            mainAttributes.put(new Attributes.Name("Agent-Class"), InstrumentationAgent.class.getName());
            mainAttributes.put(new Attributes.Name("Can-Retransform-Classes"), "true");
            mainAttributes.put(new Attributes.Name("Can-Redefine-Classes"), "true");
    
            final JarOutputStream jos = new JarOutputStream(new FileOutputStream(jarFile), manifest);
            final JarEntry agent = new JarEntry(InstrumentationAgent.class.getName().replace('.', '/') + ".class");
            jos.putNextEntry(agent);
            final ClassPool pool = ClassPool.getDefault();
            final CtClass ctClass = pool.get(InstrumentationAgent.class.getName());
            jos.write(ctClass.toBytecode());
            jos.closeEntry();
            jos.close();
    
            // Attach to VM and load the agent
            VirtualMachine vm = VirtualMachine.attach(getPidFromRuntimeMBean());
            vm.loadAgent(jarFile.getAbsolutePath());
            vm.detach();
        }
    
        private static String getPidFromRuntimeMBean() throws Exception {
            RuntimeMXBean mxbean = ManagementFactory.getRuntimeMXBean();
            Field jvmField = mxbean.getClass().getDeclaredField("jvm");
    
            jvmField.setAccessible(true);
            VMManagement management = (VMManagement) jvmField.get(mxbean);
            Method method = management.getClass().getDeclaredMethod("getProcessId");
            method.setAccessible(true);
            Integer processId = (Integer) method.invoke(management);
    
            return processId.toString();
        }
    
    }
    
    
    
    public class SystemTimeInstrumentation {
        private static long timeAdjustment = 200000L;
        private static byte[] originalClassByteArray;
    
        public static void startAdjustedClock() {
            ClassPool pool = ClassPool.getDefault();
    
            CtClass ctClass = null;
            byte[] instrumentedClassByteArray = null;
            try {
                originalClassByteArray = pool.get(System.class.getName()).toBytecode();
                ctClass = pool.makeClass(new java.io.ByteArrayInputStream(originalClassByteArray), false);
                CtMethod ctMethod = ctClass.getDeclaredMethod("currentTimeMillis");
    
                ctMethod.setBody("return 0L;");
    
                instrumentedClassByteArray = ctClass.toBytecode();
    
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (CannotCompileException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (NotFoundException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            } finally {
                if (ctClass != null) {
                    ctClass.detach();
                }
            }
    
            try {
                InstrumentationAgent.redefineClasses(new ClassDefinition[] { new ClassDefinition(System.class,
                        instrumentedClassByteArray) });
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    
        public static void stopAdjustedClock() {
            if (originalClassByteArray == null) {
                throw new RuntimeException("The stopAdjustedClock() called before startAdjustedClock()");
            } else {
                try {
                    InstrumentationAgent.redefineClasses(new ClassDefinition[] { new ClassDefinition(System.class,
                            originalClassByteArray) });
                } catch (Exception e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                originalClassByteArray = null;
            }
        }
    
    
    public class SystemTimeInstrumentationTest extends TestCase {
    
        @Test 
        public void testModifiedClock() throws Exception {
            long unmodifiedTime = System.currentTimeMillis();
            SystemTimeInstrumentation.startAdjustedClock();
            long modifiedTime = System.currentTimeMillis();
            SystemTimeInstrumentation.stopAdjustedClock();
    
            assertTrue("The difference should me more than 200000", (modifiedTime-unmodifiedTime)>200000L);
    
        }
    
    }
Working...
X