性能测试脚本的编写和调试_编写自动调试器以在测试执行期间捕获异常

性能测试脚本的编写和调试

以前,我曾说过, 您总是想保留一些调试器断点作为例外 。 这有助于防止代码在不注意的情况下腐烂掉,有时掩盖了另一个问题。

如果您认真对待这一点,那么最好将此想法扩展到自动化测试中。 但是想出一个全面的解决方案并不简单。 您可以仅从try / catch开始,但不会捕获其他线程上的异常。 您也可以使用AOP进行操作; 但是,根据框架的不同,您不能保证完全捕获所有内容,这确实意味着您正在使用稍微不同的代码进行测试,这将使您有些担心。

几天前,我遇到了有关如何编写自己的调试器的博客文章,我想知道java进程是否有可能自行调试。 事实证明,可以的,这是我作为这个小小的思想实验的一部分提出的代码。

该类的第一部分仅包含一些相当hacky的代码,用于根据启动参数来猜测连接回同一VM所需的端口。 可能可以使用Attach机制启动调试器。 但是我没有看到一种明显的方法来使其工作。 然后只有几个工厂方法带有要查找的异常列表。

package com.kingsfleet.debug;

import com.sun.jdi.Bootstrap;
import com.sun.jdi.ReferenceType;
import com.sun.jdi.VirtualMachine;
import com.sun.jdi.connect.AttachingConnector;
import com.sun.jdi.connect.Connector;
import com.sun.jdi.connect.IllegalConnectorArgumentsException;
import com.sun.jdi.event.ClassPrepareEvent;
import com.sun.jdi.event.Event;
import com.sun.jdi.event.EventQueue;
import com.sun.jdi.event.EventSet;
import com.sun.jdi.event.ExceptionEvent;
import com.sun.jdi.event.VMDeathEvent;
import com.sun.jdi.event.VMDisconnectEvent;
import com.sun.jdi.request.ClassPrepareRequest;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.ExceptionRequest;

import java.io.IOException;

import java.lang.management.ManagementFactory;
import java.lang.management.RuntimeMXBean;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

public class ExceptionDebugger implements AutoCloseable {

   public static int getDebuggerPort() {
       // Try to work out what port we need to connect to

       RuntimeMXBean runtime = ManagementFactory.getRuntimeMXBean();
       List<String> inputArguments = runtime.getInputArguments();
       int port = -1;
       boolean isjdwp = false;

       for (String next : inputArguments) {
           if (next.startsWith("-agentlib:jdwp=")) {
               isjdwp = true;
               String parameterString = next.substring("-agentlib:jdwp=".length());
               String[] parameters = parameterString.split(",");
               for (String parameter : parameters) {
                   if (parameter.startsWith("address")) {
                       int portDelimeter = parameter.lastIndexOf(":");
                       if (portDelimeter != -1) {
                           port = Integer.parseInt(parameter.substring(portDelimeter + 1));
                       } else {
                           port = Integer.parseInt(parameter.split("=")[1]);
                       }
                   }
               }
           }
       }
       return port;
   }

   public static ExceptionDebugger connect(final String... exceptions) throws InterruptedException {
       return connect(getDebuggerPort(),exceptions);
   }

   public static ExceptionDebugger connect(final int port, final String... exceptions) throws InterruptedException {

       ExceptionDebugger ed = new ExceptionDebugger(port, exceptions);

       return ed;
   }

构造函数创建一个简单的守护程序线程,以启动与虚拟机的连接。 非常重要的是,这是一个单独的线程,否则很明显,当我们遇到断点时,VM会停止运行。 确保该线程中的代码不会引发异常是一个好主意-目前,我只是希望做到最好。

最后,代码仅维护了一个禁止的异常列表,如果您有更多的时间,应该可以将堆栈跟踪存储在发生异常的位置。

// 
   // Instance variables

   private final CountDownLatch startupLatch = new CountDownLatch(1);
   private final CountDownLatch shutdownLatch = new CountDownLatch(1);

   private final Set<String> set = Collections.synchronizedSet(new HashSet<String>());
   private final int port;
   private final String exceptions[];
   private Thread debugger;
   private volatile boolean shutdown = false;

   //
   // Object construction and methods
   //

   private ExceptionDebugger(final int port, final String... exceptions) throws InterruptedException {

       this.port = port;
       this.exceptions = exceptions;

       debugger = new Thread(new Runnable() {

           @Override
           public void run() {
               try {
                   connect();
               } catch (Exception ex) {
                   ex.printStackTrace();
               }
           }
       }, "Self debugging");
       debugger.setDaemon(true); // Don't hold the VM open
       debugger.start();

       // Make sure the debugger has connected
       if (!startupLatch.await(1, TimeUnit.MINUTES)) {
           throw new IllegalStateException("Didn't connect before timeout");
       }
   }

   @Override
   public void close() throws InterruptedException {
       shutdown = true;
       // Somewhere in JDI the interrupt was being eaten, hence the volatile flag 
       debugger.interrupt();
       shutdownLatch.await();
   }

   /**
    * @return A list of exceptions that were thrown
    */
   public Set<String> getExceptionsViolated() {
       return new HashSet<String>(set);
   }

   /**
    * Clear the list of exceptions violated
    */
   public void clearExceptionsViolated() {
       set.clear();
   }

主要的连接方法是一个相当简单的代码块,可确保连接并配置任何初始断点。

//
   // Implementation details
   //

   private void connect() throws java.io.IOException {

       try {
           // Create a virtual machine connection
           VirtualMachine attach = connectToVM();

           try
           {

               // Add prepare and any already loaded exception breakpoints
               createInitialBreakpoints(attach);

               // We can now allow the rest of the work to go on as we have created the breakpoints
               // we required

               startupLatch.countDown();

               // Process the events
               processEvents(attach);
           }
           finally {

               // Disconnect the debugger
               attach.dispose();

               // Give the debugger time to really disconnect
               // before we might reconnect, couldn't find another
               // way to do this

               try {
                   TimeUnit.SECONDS.sleep(1);
               } catch (InterruptedException e) {
                   Thread.currentThread().interrupt();
               }
           }
       } finally {
           // Notify watchers that we have shutdown
           shutdownLatch.countDown();
       }
   }

重新连接到自我只是找到合适的连接器的过程,在本例中是Socket,尽管我猜想如果您稍稍修改一下代码,便可以在某些平台上使用共享内存传输。

private VirtualMachine connectToVM() throws java.io.IOException {

       List<AttachingConnector> attachingConnectors = Bootstrap.virtualMachineManager().attachingConnectors();
       AttachingConnector ac = null;

       found:
       for (AttachingConnector next : attachingConnectors) {
           if (next.name().contains("SocketAttach")) {
               ac = next;
               break;

           }
       }

       Map<String, Connector.Argument> arguments = ac.defaultArguments();
       arguments.get("hostname").setValue("localhost");
       arguments.get("port").setValue(Integer.toString(port));
       arguments.get("timeout").setValue("4000");

       try {
           return ac.attach(arguments);
       } catch (IllegalConnectorArgumentsException e) {
           throw new IOException("Problem connecting to debugger",e);
       }
   }

连接调试器时,您不知道是否已加载您感兴趣的异常,因此您需要为准备类的点和已经加载的点注册断点。

请注意,设置的断点仅用于中断一个线程的策略–否则,出于显而易见的原因,如果调试器线程也进入睡眠状态,则当前VM将停止运行。

private void createInitialBreakpoints(VirtualMachine attach) {
       // Our first exception is for class loading

       for (String exception : exceptions) {
           ClassPrepareRequest cpr = attach.eventRequestManager().createClassPrepareRequest();
           cpr.addClassFilter(exception);
           cpr.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
           cpr.setEnabled(true);
       }

       // Then we can check each in turn to see if it have already been loaded as we might
       // be late to the game, remember classes can be loaded more than once
       //

       for (String exception : exceptions) {
           List<ReferenceType> types = attach.classesByName(exception);
           for (ReferenceType type : types) {
               createExceptionRequest(attach, type);
           }
       }
   }

   private static void createExceptionRequest(VirtualMachine attach, 
                                              ReferenceType refType) {
       ExceptionRequest er = attach.eventRequestManager().createExceptionRequest(
           refType, true, true);
       er.setSuspendPolicy(EventRequest.SUSPEND_EVENT_THREAD);
       er.setEnabled(true);
   }

事件处理循环轮询包含一个或多个事件实例的EventSet实例。 尽管并非所有这些事件都属于断点请求,所以您必须注意不要总是在事件集上调用简历。 这是因为您可能连续有两个事件集,而代码甚至在阅读第二个事件集之前就调用了resume。 随着代码的赶上,这会导致错过断点。

由于某种原因, JDI似乎正在吃掉中断的标志,因此该布尔属性使用以前的close方法停止循环。

private void processEvents(VirtualMachine attach) {
       // Listen for events

       EventQueue eq = attach.eventQueue();
       eventLoop: while (!Thread.interrupted() && !shutdown) {

           // Poll for event sets, with a short timeout so that we can
           // be interrupted if required
           EventSet eventSet = null;
           try 
           {
               eventSet = eq.remove(500);
           }
           catch (InterruptedException ex) {
               Thread.currentThread().interrupt();
               continue eventLoop;  
           }

           // Just loop again if we have no events
           if (eventSet == null) {
               continue eventLoop;
           }

           //

           boolean resume = false;
           for (Event event : eventSet) {

               EventRequest request = event.request();
               if (request != null) {
                   int eventPolicy = request.suspendPolicy();
                   resume |= eventPolicy != EventRequest.SUSPEND_NONE;
               }

               if (event instanceof VMDeathEvent || event instanceof VMDisconnectEvent) {
                   // This should never happen as the VM will exit before this is called

               } else if (event instanceof ClassPrepareEvent) {

                   // When an instance of the exception class is loaded attach an exception breakpoint
                   ClassPrepareEvent cpe = (ClassPrepareEvent) event;
                   ReferenceType refType = cpe.referenceType();
                   createExceptionRequest(attach, refType);

               } else if (event instanceof ExceptionEvent) {

                   String name = ((ExceptionRequest)event.request()).exception().name();
                   set.add(name);
               }
           }

           // Dangerous to call resume always because not all event suspend the VM
           // and events happen asynchornously.
           if (resume)
               eventSet.resume();
       }
   }

}

因此,剩下的只是一个简单的测试示例,因为这是JDK 7,而ExceptionDebugger是AutoCloseable,所以我们可以使用try-with-resources构造进行此操作,如下所示。 显然,如果要进行自动化测试,请使用您选择的测试框架固定装置。

public class Target {

   public static void main(String[] args) throws InterruptedException {

       try (ExceptionDebugger ex = ExceptionDebugger.connect(
               NoClassDefFoundError.class.getName())) {

           doSomeWorkThatQuietlyThrowsAnException();

           System.out.println(ex.getExceptionsViolated());
       }

       System.exit(0);
   }

   private static void doSomeWorkThatQuietlyThrowsAnException() {
       // Check to see that break point gets fired

       try {
           Thread t = new Thread(new Runnable() {
                           public void run() {
                               try
                               {
                                   throw new NoClassDefFoundError();
                               }
                               catch (Throwable ex) {

                               }
                           }
                      });
           t.start();
           t.join();
       } catch (Throwable th) {
           // Eat this and don't tell anybody
       }
   }
}

因此,如果使用以下VM参数运行此类,请注意suspend = n,否则代码将不会开始运行,您会发现它可以重新连接到自身并开始运行。

-agentlib:jdwp=transport=dt_socket,address=localhost:5656,server=y,suspend=n

这将为您提供以下输出,请注意来自VM的额外调试行:

Listening for transport dt_socket at address: 5656
 java.lang.NoClassDefFoundError 
Listening for transport dt_socket at address: 5656

每个人都想读一下这是否对人们有用并有助于消除任何明显的错误。


翻译自: https://www.javacodegeeks.com/2013/10/write-an-auto-debugger-to-catch-exceptions-during-test-execution.html

性能测试脚本的编写和调试

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值