001/* 002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl-2.1.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Florent Guillaume 016 */ 017package org.nuxeo.runtime.test.runner; 018 019import java.io.IOException; 020import java.io.InputStream; 021import java.util.Collections; 022import java.util.Comparator; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026 027import org.junit.runners.model.FrameworkMethod; 028import org.objectweb.asm.ClassReader; 029import org.objectweb.asm.ClassVisitor; 030import org.objectweb.asm.Label; 031import org.objectweb.asm.MethodVisitor; 032import org.objectweb.asm.Opcodes; 033 034/** 035 * Utility class that sorts a list of JUnit methods according to their source line number. 036 * 037 * @since 7.1 038 */ 039public class MethodSorter { 040 041 private MethodSorter() { 042 // utility class 043 } 044 045 /** 046 * Sorts a list of JUnit methods according to their source line number. 047 * 048 * @param methods the JUnit methods 049 */ 050 public static void sortMethodsUsingSourceOrder(List<FrameworkMethod> methods) { 051 if (methods.isEmpty()) { 052 return; 053 } 054 Map<String, Integer> nameToLine = new HashMap<>(); 055 Class<?> cls = methods.get(0).getMethod().getDeclaringClass(); 056 String name = "/" + cls.getName().replace(".", "/") + ".class"; 057 try (InputStream is = cls.getResourceAsStream(name)) { 058 ClassReader cr = new ClassReader(is); 059 ClassVisitor cv = new CV(nameToLine); 060 cr.accept(cv, ClassReader.SKIP_FRAMES); 061 } catch (IOException e) { 062 throw new RuntimeException("Failed to parse " + name, e); 063 } 064 Collections.sort(methods, new LineComparator(nameToLine)); 065 } 066 067 /** 068 * Class Visitor that constructs a map of method name to source line number. 069 */ 070 public static class CV extends ClassVisitor { 071 072 public Map<String, Integer> nameToLine; 073 074 public CV(Map<String, Integer> nameToLine) { 075 super(Opcodes.ASM5); 076 this.nameToLine = nameToLine; 077 } 078 079 @Override 080 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 081 return new MV(nameToLine, name); 082 } 083 } 084 085 /** 086 * Method Visitor that records method source line number. 087 */ 088 public static class MV extends MethodVisitor { 089 090 public Map<String, Integer> nameToLine; 091 092 public final String name; 093 094 public MV(Map<String, Integer> nameToLine, String name) { 095 super(Opcodes.ASM5); 096 this.nameToLine = nameToLine; 097 this.name = name; 098 } 099 100 @Override 101 public void visitLineNumber(int line, Label start) { 102 if (nameToLine.get(name) == null) { 103 nameToLine.put(name, Integer.valueOf(line)); 104 } 105 } 106 } 107 108 /** 109 * Comparator of methods according to their line number. 110 */ 111 public static class LineComparator implements Comparator<FrameworkMethod> { 112 113 public Map<String, Integer> nameToLine; 114 115 public LineComparator(Map<String, Integer> nameToLine) { 116 this.nameToLine = nameToLine; 117 } 118 119 @Override 120 public int compare(FrameworkMethod fm1, FrameworkMethod fm2) { 121 String name1 = fm1.getMethod().getName(); 122 String name2 = fm2.getMethod().getName(); 123 Integer pos1 = nameToLine.get(name1); 124 Integer pos2 = nameToLine.get(name2); 125 if (pos1 == null || pos2 == null) { 126 return name1.compareTo(name2); 127 } else { 128 return pos1.compareTo(pos2); 129 } 130 } 131 } 132 133}