/*
* (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Florent Guillaume
*/
package org.nuxeo.runtime.test.runner;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.junit.runners.model.FrameworkMethod;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
/**
* Utility class that sorts a list of JUnit methods according to their source line number.
*
* @since 7.1
*/
public class MethodSorter {
private MethodSorter() {
// utility class
}
/**
* Sorts a list of JUnit methods according to their source line number.
*
* @param methods the JUnit methods
*/
public static void sortMethodsUsingSourceOrder(List<FrameworkMethod> methods) {
if (methods.isEmpty()) {
return;
}
Map<String, Integer> nameToLine = new HashMap<>();
Class<?> cls = methods.get(0).getMethod().getDeclaringClass();
String name = "/" + cls.getName().replace(".", "/") + ".class";
try (InputStream is = cls.getResourceAsStream(name)) {
ClassReader cr = new ClassReader(is);
ClassVisitor cv = new CV(nameToLine);
cr.accept(cv, ClassReader.SKIP_FRAMES);
} catch (IOException e) {
throw new RuntimeException("Failed to parse " + name, e);
}
Collections.sort(methods, new LineComparator(nameToLine));
}
/**
* Class Visitor that constructs a map of method name to source line number.
*/
public static class CV extends ClassVisitor {
public Map<String, Integer> nameToLine;
public CV(Map<String, Integer> nameToLine) {
super(Opcodes.ASM5);
this.nameToLine = nameToLine;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
return new MV(nameToLine, name);
}
}
/**
* Method Visitor that records method source line number.
*/
public static class MV extends MethodVisitor {
public Map<String, Integer> nameToLine;
public final String name;
public MV(Map<String, Integer> nameToLine, String name) {
super(Opcodes.ASM5);
this.nameToLine = nameToLine;
this.name = name;
}
@Override
public void visitLineNumber(int line, Label start) {
if (nameToLine.get(name) == null) {
nameToLine.put(name, Integer.valueOf(line));
}
}
}
/**
* Comparator of methods according to their line number.
*/
public static class LineComparator implements Comparator<FrameworkMethod> {
public Map<String, Integer> nameToLine;
public LineComparator(Map<String, Integer> nameToLine) {
this.nameToLine = nameToLine;
}
@Override
public int compare(FrameworkMethod fm1, FrameworkMethod fm2) {
String name1 = fm1.getMethod().getName();
String name2 = fm2.getMethod().getName();
Integer pos1 = nameToLine.get(name1);
Integer pos2 = nameToLine.get(name2);
if (pos1 == null || pos2 == null) {
return name1.compareTo(name2);
} else {
return pos1.compareTo(pos2);
}
}
}
}