001/* 002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * Florent Guillaume 018 */ 019package org.nuxeo.runtime.test.runner; 020 021import java.io.IOException; 022import java.io.InputStream; 023import java.util.Collections; 024import java.util.Comparator; 025import java.util.HashMap; 026import java.util.List; 027import java.util.Map; 028 029import org.junit.runners.model.FrameworkMethod; 030import org.objectweb.asm.ClassReader; 031import org.objectweb.asm.ClassVisitor; 032import org.objectweb.asm.Label; 033import org.objectweb.asm.MethodVisitor; 034import org.objectweb.asm.Opcodes; 035 036/** 037 * Utility class that sorts a list of JUnit methods according to their source line number. 038 * 039 * @since 7.1 040 */ 041public class MethodSorter { 042 043 private MethodSorter() { 044 // utility class 045 } 046 047 /** 048 * Sorts a list of JUnit methods according to their source line number. 049 * 050 * @param methods the JUnit methods 051 */ 052 public static void sortMethodsUsingSourceOrder(List<FrameworkMethod> methods) { 053 if (methods.isEmpty()) { 054 return; 055 } 056 Map<String, Integer> nameToLine = new HashMap<>(); 057 Class<?> cls = methods.get(0).getMethod().getDeclaringClass(); 058 String name = "/" + cls.getName().replace(".", "/") + ".class"; 059 try (InputStream is = cls.getResourceAsStream(name)) { 060 ClassReader cr = new ClassReader(is); 061 ClassVisitor cv = new CV(nameToLine); 062 cr.accept(cv, ClassReader.SKIP_FRAMES); 063 } catch (IOException e) { 064 throw new RuntimeException("Failed to parse " + name, e); 065 } 066 Collections.sort(methods, new LineComparator(nameToLine)); 067 } 068 069 /** 070 * Class Visitor that constructs a map of method name to source line number. 071 */ 072 public static class CV extends ClassVisitor { 073 074 public Map<String, Integer> nameToLine; 075 076 public CV(Map<String, Integer> nameToLine) { 077 super(Opcodes.ASM5); 078 this.nameToLine = nameToLine; 079 } 080 081 @Override 082 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { 083 return new MV(nameToLine, name); 084 } 085 } 086 087 /** 088 * Method Visitor that records method source line number. 089 */ 090 public static class MV extends MethodVisitor { 091 092 public Map<String, Integer> nameToLine; 093 094 public final String name; 095 096 public MV(Map<String, Integer> nameToLine, String name) { 097 super(Opcodes.ASM5); 098 this.nameToLine = nameToLine; 099 this.name = name; 100 } 101 102 @Override 103 public void visitLineNumber(int line, Label start) { 104 if (nameToLine.get(name) == null) { 105 nameToLine.put(name, Integer.valueOf(line)); 106 } 107 } 108 } 109 110 /** 111 * Comparator of methods according to their line number. 112 */ 113 public static class LineComparator implements Comparator<FrameworkMethod> { 114 115 public Map<String, Integer> nameToLine; 116 117 public LineComparator(Map<String, Integer> nameToLine) { 118 this.nameToLine = nameToLine; 119 } 120 121 @Override 122 public int compare(FrameworkMethod fm1, FrameworkMethod fm2) { 123 String name1 = fm1.getMethod().getName(); 124 String name2 = fm2.getMethod().getName(); 125 Integer pos1 = nameToLine.get(name1); 126 Integer pos2 = nameToLine.get(name2); 127 if (pos1 == null || pos2 == null) { 128 return name1.compareTo(name2); 129 } else { 130 return pos1.compareTo(pos2); 131 } 132 } 133 } 134 135}