Skip to main content
summaryrefslogblamecommitdiffstats
blob: 42d61fdec6dc94de964b62c16266d38c916dc66b (plain) (tree)

























                                                                                 
                                                                               












                                                                                
                                                                                  







                                                          
                                                                                  
























































                                                                                                                           




                                                                                                









                                                                                                                              
                                           

























                                                                                                                                           
                                                 


















































                                                                                               













                                                                                     
/*******************************************************************************
 * Copyright (c) 2012 IBM Corporation and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.ui.workbench.texteditor.tests;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;

import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

/**
 * Test suite that contains the same tests as a regular {@link TestSuite},
 * but the order of tests is the order as declared in the classfile's bytecode.
 * 
 * <p>
 * <b>Background:</b> {@link java.lang.Class#getDeclaredMethods()} does not
 * specify the order of the methods. Up to JavaSE 6, the methods were usually
 * sorted in declaration order, but in JavaSE 7, the order is random. This class
 * guarantees reliable test execution order.
 * </p>
 * 
 * @since 3.9
 */
public class BytecodeOrderedTestSuite extends TestSuite {
	
	/**
	 * Creates a new test suite that runs tests in bytecode declaration order.
	 * 
	 * @param testClass the JUnit-3-style test class 
	 */
	public BytecodeOrderedTestSuite(Class testClass) {
		this(testClass, testClass.getName());
	}
	
	/**
	 * Creates a new test suite that runs tests in bytecode declaration order.
	 * 
	 * @param testClass the JUnit-3-style test class 
	 * @param name the name of the suite
	 */
	public BytecodeOrderedTestSuite(Class testClass, String name) {
    	super(name);
    	
    	TestSuite randomOrderSuite= new TestSuite(testClass);
    	ArrayList tests= Collections.list(randomOrderSuite.tests());
    	
    	class SortingException extends RuntimeException {
			private static final long serialVersionUID= 1L;
			public SortingException(String message) {
    			super(message);
    		}
    	}
    	final ArrayList orderedMethodNames= new ArrayList();
    	Class c= testClass;
    	try {
    		while (Test.class.isAssignableFrom(c)) {
    			addDeclaredTestMethodNames(c, orderedMethodNames);
    			c= c.getSuperclass();
    		}
    		Collections.sort(tests, new Comparator() {
				public int compare(Object o1, Object o2) {
					if (o1 instanceof TestCase && o2 instanceof TestCase) {
						TestCase t1= (TestCase) o1;
						TestCase t2= (TestCase) o2;
						int i1= orderedMethodNames.indexOf(t1.getName());
						int i2= orderedMethodNames.indexOf(t2.getName());
						if (i1 != -1 && i2 != -1)
							return i1 - i2;
					}
					throw new SortingException("suite failed to detect test order: " + o1 + ", " + o2);
				}
			});
    	} catch (SortingException e) {
    		addTest(error(testClass, "suite failed to detect test order", e)); //$NON-NLS-1$
    	} catch (IOException e) {
    		addTest(error(testClass, "suite failed to detect test order", e)); //$NON-NLS-1$
    	}
    	
    	for (Iterator iter= tests.iterator(); iter.hasNext(); ) {
			Test test= (Test) iter.next();
			addTest(test);
		}
	}

	private static Test error(Class testClass, String testMethod, Exception exception) {
		final Throwable e2= exception.fillInStackTrace();
		return new TestCase(testMethod + "(" + testClass.getName() + ")") { //$NON-NLS-1$ //$NON-NLS-2$
			protected void runTest() throws Throwable {
				throw e2;
			}
		};
	}
	
    private void addDeclaredTestMethodNames(Class c, ArrayList methodNames) throws IOException {
    	/*
    	 * XXX: This method needs to be updated if a new major class file version
    	 * or new constant pool tags are specified.
    	 */
        String className= c.getName();
        int lastDot= className.lastIndexOf(".");
		if (lastDot != -1)
        	className= className.substring(lastDot + 1);
		DataInputStream is= new DataInputStream(new BufferedInputStream(c.getResourceAsStream(className + ".class")));
        int magic= is.readInt();
        if (magic != 0xcafebabe)
            throw new IOException("bad magic bytes: 0x" + Integer.toHexString(magic));
        skip(is, 2); // minor_version
        int major= is.readUnsignedShort();
        if (major > 51) { // major > Java 7
        	addTest(error(c, "suite can't handle class file version", new RuntimeException(c.getName() + " (major = " + major + ")")));
        }
        int cpCount= is.readUnsignedShort();
        String[] constantPoolStrings= new String[cpCount];
        for (int i= 1; i < cpCount; i++) {

            byte tag= is.readByte();
            switch (tag) {
                case 7: // CONSTANT_Class
                    skip(is, 2);
                    break;
                case 9: // CONSTANT_Fieldref
                case 10: // CONSTANT_Methodref
                case 11: // CONSTANT_InterfaceMethodref
                    skip(is, 4);
                    break;
                case 8: // CONSTANT_String
                    skip(is, 2);
                    break;
                case 3: // CONSTANT_Integer
                case 4: // CONSTANT_Float
                    skip(is, 4);
                    break;
                case 5: // CONSTANT_Long
                case 6: // CONSTANT_Double
                    skip(is, 8);
                    i++; // weird spec wants this
                    break;
                case 12: // CONSTANT_NameAndType
                    skip(is, 4);
                    break;
                case 1: // CONSTANT_Utf8
                    constantPoolStrings[i]= is.readUTF();
                    break;
                case 15: // CONSTANT_MethodHandle
                    skip(is, 3);
                    break;
                case 16: // CONSTANT_MethodType
                    skip(is, 2);
                    break;
                case 18: // CONSTANT_InvokeDynamic
                    skip(is, 4);
                    break;
                default:
                	throw new IOException("unknown constant pool tag (" + i + "): " + tag);
            }
        }
        skip(is, 2 * 3); // access_flags, this_class, super_class
        int interfacesCount= is.readUnsignedShort();
        skip(is, 2 * interfacesCount);
        int fieldsCount= is.readUnsignedShort();
        for (int i= 0; i < fieldsCount; i++) {
            skip(is, 2 * 3); // access_flags, name_index, descriptor_index
            int attributesCount= is.readUnsignedShort();
            for (int j= 0; j < attributesCount; j++) {
                skip(is, 2); // attribute_name_index
                long attInfoCount= readUnsignedInt(is);
                skip(is, attInfoCount);
            }
        }

        int methodsCount= is.readUnsignedShort();
        for (int i= 0; i < methodsCount; i++) {
            skip(is, 2); // access_flags
            int nameIndex= is.readUnsignedShort();
            int descIndex= is.readUnsignedShort();
            if ("()V".equals(constantPoolStrings[descIndex])) {
				String name= constantPoolStrings[nameIndex];
				if (name.startsWith("test"))
					methodNames.add(name);
			}
            int attributesCount= is.readUnsignedShort();
            for (int j= 0; j < attributesCount; j++) {
                skip(is, 2); // attribute_name_index
                long attInfoCount= readUnsignedInt(is);
                skip(is, attInfoCount);
            }
        }
    }
    
	private static void skip(DataInputStream is, long bytes) throws IOException {
		while (bytes > 0)
			bytes -= is.skip(bytes);
		if (bytes != 0)
			throw new IOException("error in skipping bytes: " + bytes);
	}

	private static long readUnsignedInt(DataInputStream is) throws IOException {
		return is.readInt() & 0xFFFFffffL;
	}

}

Back to the top