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}