Skip to main content
summaryrefslogtreecommitdiffstats
blob: 42d61fdec6dc94de964b62c16266d38c916dc66b (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
/*******************************************************************************
 * 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