1 /*******************************************************************************
2  * Copyright (c) 2005, 2015 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Alexander Kurtakov <akurtako@redhat.com> - bug 458490
14  *******************************************************************************/
15 package org.eclipse.core.tests.runtime.perf;
16 
17 import java.io.*;
18 import java.net.MalformedURLException;
19 import java.net.URL;
20 import junit.framework.Test;
21 import junit.framework.TestSuite;
22 import org.eclipse.core.internal.content.*;
23 import org.eclipse.core.runtime.IPath;
24 import org.eclipse.core.runtime.Platform;
25 import org.eclipse.core.runtime.content.*;
26 import org.eclipse.core.runtime.preferences.InstanceScope;
27 import org.eclipse.core.tests.harness.*;
28 import org.eclipse.core.tests.runtime.RuntimeTest;
29 import org.eclipse.core.tests.runtime.RuntimeTestsPlugin;
30 import org.eclipse.core.tests.session.PerformanceSessionTestSuite;
31 import org.eclipse.core.tests.session.SessionTestSuite;
32 import org.osgi.framework.Bundle;
33 import org.osgi.framework.BundleException;
34 
35 public class ContentTypePerformanceTest extends RuntimeTest {
36 
37 	private final static String CONTENT_TYPE_PREF_NODE = Platform.PI_RUNTIME + IPath.SEPARATOR + "content-types"; //$NON-NLS-1$
38 	private static final String DEFAULT_NAME = "file_" + ContentTypePerformanceTest.class.getName();
39 	private static final int ELEMENTS_PER_LEVEL = 4;
40 	private static final int NUMBER_OF_LEVELS = 4;
41 	private static final String TEST_DATA_ID = "org.eclipse.core.tests.runtime.contenttype.perf.testdata";
42 	private static final int TOTAL_NUMBER_OF_ELEMENTS = computeTotalTypes(NUMBER_OF_LEVELS, ELEMENTS_PER_LEVEL);
43 
computeTotalTypes(int levels, int elementsPerLevel)44 	private static int computeTotalTypes(int levels, int elementsPerLevel) {
45 		double sum = 0;
46 		for (int i = 0; i <= levels; i++) {
47 			sum += Math.pow(elementsPerLevel, i);
48 		}
49 		return (int) sum;
50 	}
51 
createContentType(Writer writer, int number, String baseTypeId)52 	private static String createContentType(Writer writer, int number, String baseTypeId) throws IOException {
53 		String id = "performance" + number;
54 		String definition = generateContentType(number, id, baseTypeId, new String[] {DEFAULT_NAME}, null);
55 		writer.write(definition);
56 		writer.write(System.lineSeparator());
57 		return id;
58 	}
59 
createContentTypes(Writer writer, String baseTypeId, int created, int numberOfLevels, int nodesPerLevel)60 	public static int createContentTypes(Writer writer, String baseTypeId, int created, int numberOfLevels, int nodesPerLevel) throws IOException {
61 		if (numberOfLevels == 0) {
62 			return 0;
63 		}
64 		int local = nodesPerLevel;
65 		for (int i = 0; i < nodesPerLevel; i++) {
66 			String id = createContentType(writer, created + i, baseTypeId);
67 			local += createContentTypes(writer, id, created + local, numberOfLevels - 1, nodesPerLevel);
68 		}
69 		return local;
70 	}
71 
generateContentType(int number, String id, String baseTypeId, String[] fileNames, String[] fileExtensions)72 	private static String generateContentType(int number, String id, String baseTypeId, String[] fileNames, String[] fileExtensions) {
73 		StringBuilder result = new StringBuilder();
74 		result.append("<content-type id=\"");
75 		result.append(id);
76 		result.append("\" name=\"");
77 		result.append(id);
78 		result.append("\" ");
79 		if (baseTypeId != null) {
80 			result.append("base-type=\"");
81 			result.append(baseTypeId);
82 			result.append("\" ");
83 		}
84 		String fileNameList = Util.toListString(fileNames);
85 		if (fileNameList != null) {
86 			result.append("file-names=\"");
87 			result.append(fileNameList);
88 			result.append("\" ");
89 		}
90 		String fileExtensionsList = Util.toListString(fileExtensions);
91 		if (fileExtensions != null && fileExtensions.length > 0) {
92 			result.append("file-extensions=\"");
93 			result.append(fileExtensionsList);
94 			result.append("\" ");
95 		}
96 		result.append("describer=\"");
97 		result.append(BinarySignatureDescriber.class.getName());
98 		result.append(":");
99 		result.append(getSignatureString(number));
100 		result.append("\"/>");
101 		return result.toString();
102 	}
103 
getContentTypeId(int i)104 	private static String getContentTypeId(int i) {
105 		return TEST_DATA_ID + ".performance" + i;
106 	}
107 
getSignature(int number)108 	private static byte[] getSignature(int number) {
109 		byte[] result = new byte[4];
110 		for (int i = 0; i < result.length; i++) {
111 			result[i] = (byte) ((number >> (i * 8)) & 0xFFL);
112 		}
113 		return result;
114 	}
115 
getSignatureString(int number)116 	private static String getSignatureString(int number) {
117 		byte[] signature = getSignature(number);
118 		StringBuilder result = new StringBuilder(signature.length * 3 - 1);
119 		for (byte element : signature) {
120 			result.append(Integer.toHexString(0xFF & element));
121 			result.append(' ');
122 		}
123 		result.deleteCharAt(result.length() - 1);
124 		return result.toString();
125 	}
126 
suite()127 	public static Test suite() {
128 		TestSuite suite = new TestSuite(ContentTypePerformanceTest.class.getName());
129 
130 		//		suite.addTest(new ContentTypePerformanceTest("testDoSetUp"));
131 		//		suite.addTest(new ContentTypePerformanceTest("testContentMatching"));
132 		//		suite.addTest(new ContentTypePerformanceTest("testContentTXTMatching"));
133 		//		suite.addTest(new ContentTypePerformanceTest("testContentXMLMatching"));
134 		//		suite.addTest(new ContentTypePerformanceTest("testDoTearDown"));
135 
136 		SessionTestSuite setUp = new SessionTestSuite(PI_RUNTIME_TESTS, "testDoSetUp");
137 		setUp.addTest(new ContentTypePerformanceTest("testDoSetUp"));
138 		suite.addTest(setUp);
139 
140 		TestSuite singleRun = new PerformanceSessionTestSuite(PI_RUNTIME_TESTS, 1, "singleSessionTests");
141 		singleRun.addTest(new ContentTypePerformanceTest("testContentMatching"));
142 		singleRun.addTest(new ContentTypePerformanceTest("testNameMatching"));
143 		singleRun.addTest(new ContentTypePerformanceTest("testIsKindOf"));
144 		suite.addTest(singleRun);
145 
146 		TestSuite loadCatalog = new PerformanceSessionTestSuite(PI_RUNTIME_TESTS, 10, "multipleSessionTests");
147 		loadCatalog.addTest(new ContentTypePerformanceTest("testLoadCatalog"));
148 		suite.addTest(loadCatalog);
149 
150 		TestSuite tearDown = new SessionTestSuite(PI_RUNTIME_TESTS, "testDoTearDown");
151 		tearDown.addTest(new ContentTypePerformanceTest("testDoTearDown"));
152 		suite.addTest(tearDown);
153 		return suite;
154 	}
155 
ContentTypePerformanceTest(String name)156 	public ContentTypePerformanceTest(String name) {
157 		super(name);
158 	}
159 
countTestContentTypes(IContentType[] all)160 	private int countTestContentTypes(IContentType[] all) {
161 		String namespace = TEST_DATA_ID + '.';
162 		int count = 0;
163 		for (IContentType element : all) {
164 			if (element.getId().startsWith(namespace)) {
165 				count++;
166 			}
167 		}
168 		return count;
169 	}
170 
getExtraPluginLocation()171 	public IPath getExtraPluginLocation() {
172 		return getTempDir().append(TEST_DATA_ID);
173 	}
174 
installContentTypes(String tag, int numberOfLevels, int nodesPerLevel)175 	private Bundle installContentTypes(String tag, int numberOfLevels, int nodesPerLevel) {
176 		TestRegistryChangeListener listener = new TestRegistryChangeListener(Platform.PI_RUNTIME, ContentTypeBuilder.PT_CONTENTTYPES, null, null);
177 		Bundle installed = null;
178 		listener.register();
179 		try {
180 			IPath pluginLocation = getExtraPluginLocation();
181 			assertTrue(pluginLocation.toFile().mkdirs());
182 			assertTrue(pluginLocation.append("META-INF").toFile().mkdirs());
183 			URL installURL = null;
184 			try {
185 				installURL = pluginLocation.toFile().toURI().toURL();
186 			} catch (MalformedURLException e) {
187 				fail(tag + ".0.5", e);
188 			}
189 			String eol = System.lineSeparator();
190 			try (Writer writer = new BufferedWriter(new FileWriter(pluginLocation.append("plugin.xml").toFile()),
191 					0x10000)) {
192 				writer.write("<plugin>");
193 				writer.write(eol);
194 				writer.write("<extension point=\"org.eclipse.core.runtime.contentTypes\">");
195 				writer.write(eol);
196 				String root = createContentType(writer, 0, null);
197 				createContentTypes(writer, root, 1, numberOfLevels, nodesPerLevel);
198 				writer.write("</extension></plugin>");
199 			} catch (IOException e) {
200 				fail(tag + ".1.0", e);
201 			}
202 			try (Writer writer = new BufferedWriter(
203 					new FileWriter(pluginLocation.append("META-INF").append("MANIFEST.MF").toFile()),
204 					0x10000)) {
205 				writer.write("Manifest-Version: 1.0");
206 				writer.write(eol);
207 				writer.write("Bundle-ManifestVersion: 2");
208 				writer.write(eol);
209 				writer.write("Bundle-Name: Content Type Performance Test Data");
210 				writer.write(eol);
211 				writer.write("Bundle-SymbolicName: " + TEST_DATA_ID + "; singleton:=true");
212 				writer.write(eol);
213 				writer.write("Bundle-Version: 1.0\n");
214 				writer.write("Require-Bundle: " + PI_RUNTIME_TESTS);
215 				writer.write(eol);
216 			} catch (IOException e) {
217 				fail(tag + ".2.0", e);
218 			}
219 			try {
220 				installed = RuntimeTestsPlugin.getContext().installBundle(installURL.toExternalForm());
221 			} catch (BundleException e) {
222 				fail(tag + ".3.0", e);
223 			}
224 			BundleTestingHelper.refreshPackages(RuntimeTestsPlugin.getContext(), new Bundle[] {installed});
225 			assertTrue(tag + ".4.0", listener.eventReceived(10000));
226 		} finally {
227 			listener.unregister();
228 		}
229 		return installed;
230 	}
231 
232 	/**
233 	 * Warms up the content type registry.
234 	 */
loadChildren()235 	private void loadChildren() {
236 		final IContentTypeManager manager = Platform.getContentTypeManager();
237 		IContentType[] allTypes = manager.getAllContentTypes();
238 		for (IContentType allType : allTypes) {
239 			String[] fileNames = allType.getFileSpecs(IContentType.IGNORE_USER_DEFINED | IContentType.FILE_NAME_SPEC);
240 			for (String fileName : fileNames) {
241 				manager.findContentTypeFor(fileName);
242 			}
243 			String[] fileExtensions = allType.getFileSpecs(IContentType.IGNORE_USER_DEFINED | IContentType.FILE_EXTENSION_SPEC);
244 			for (String fileExtension : fileExtensions) {
245 				manager.findContentTypeFor("anyname." + fileExtension);
246 			}
247 		}
248 	}
249 
250 	/**
251 	 * Returns a loaded content type manager. Except for load time tests, this method should
252 	 * be called outside the scope of performance monitoring.
253 	 */
loadContentTypeManager()254 	private IContentTypeManager loadContentTypeManager() {
255 		// any cheap interaction that causes the catalog to be built
256 		Platform.getContentTypeManager().getContentType(IContentTypeManager.CT_TEXT);
257 		return Platform.getContentTypeManager();
258 	}
259 
260 	/** Forces all describers to be loaded.*/
loadDescribers()261 	private void loadDescribers() {
262 		final IContentTypeManager manager = Platform.getContentTypeManager();
263 		IContentType[] allTypes = manager.getAllContentTypes();
264 		for (IContentType allType : allTypes) {
265 			((ContentTypeHandler) allType).getTarget().getDescriber();
266 		}
267 	}
268 
loadPreferences()269 	private void loadPreferences() {
270 		InstanceScope.INSTANCE.getNode(CONTENT_TYPE_PREF_NODE);
271 	}
272 
273 	/** Tests how much the size of the catalog affects the performance of content type matching by content analysis */
testContentMatching()274 	public void testContentMatching() {
275 		loadPreferences();
276 		// warm up content type registry
277 		final IContentTypeManager manager = loadContentTypeManager();
278 		loadDescribers();
279 		loadChildren();
280 		new PerformanceTestRunner() {
281 			@Override
282 			protected void test() {
283 				try {
284 					for (int i = 0; i < TOTAL_NUMBER_OF_ELEMENTS; i++) {
285 						String id = getContentTypeId(i);
286 						IContentType[] result = manager.findContentTypesFor(new ByteArrayInputStream(getSignature(i)), DEFAULT_NAME);
287 						assertEquals("1.0." + i, 1, result.length);
288 						assertEquals("1.1." + i, id, result[0].getId());
289 					}
290 				} catch (IOException e) {
291 					fail("2.0", e);
292 				}
293 			}
294 		}.run(this, 10, 2);
295 	}
296 
297 	@Override
setUp()298 	protected void setUp() throws Exception {
299 		super.setUp();
300 		if (getName().equals("testDoSetUp") || getName().equals("testDoTearDown")) {
301 			return;
302 		}
303 		Bundle installed = null;
304 		try {
305 			installed = RuntimeTestsPlugin.getContext()
306 					.installBundle(getExtraPluginLocation().toFile().toURI().toURL().toExternalForm());
307 		} catch (BundleException e) {
308 			fail("1.0", e);
309 		} catch (MalformedURLException e) {
310 			fail("2.0", e);
311 		}
312 		BundleTestingHelper.refreshPackages(RuntimeTestsPlugin.getContext(), new Bundle[] { installed });
313 	}
314 
testDoSetUp()315 	public void testDoSetUp() {
316 		installContentTypes("1.0", NUMBER_OF_LEVELS, ELEMENTS_PER_LEVEL);
317 	}
318 
testDoTearDown()319 	public void testDoTearDown() {
320 		ensureDoesNotExistInFileSystem(getExtraPluginLocation().toFile());
321 	}
322 
testIsKindOf()323 	public void testIsKindOf() {
324 		// warm up preference service
325 		loadPreferences();
326 		// warm up content type registry
327 		final IContentTypeManager manager = loadContentTypeManager();
328 		loadChildren();
329 		final IContentType root = manager.getContentType(getContentTypeId(0));
330 		assertNotNull("2.0", root);
331 		new PerformanceTestRunner() {
332 			@Override
333 			protected void test() {
334 				for (int i = 0; i < TOTAL_NUMBER_OF_ELEMENTS; i++) {
335 					IContentType type = manager.getContentType(getContentTypeId(i));
336 					assertNotNull("3.0." + i, type);
337 					assertTrue("3.1." + i, type.isKindOf(root));
338 				}
339 			}
340 		}.run(this, 10, 500);
341 	}
342 
343 	/**
344 	 * This test is intended for running as a session test.
345 	 */
testLoadCatalog()346 	public void testLoadCatalog() {
347 		// warm up preference service
348 		loadPreferences();
349 		PerformanceTestRunner runner = new PerformanceTestRunner() {
350 			@Override
351 			protected void test() {
352 				// any interation that will cause the registry to be loaded
353 				Platform.getContentTypeManager().getContentType(IContentTypeManager.CT_TEXT);
354 			}
355 		};
356 		runner.run(this, 1, /* must run only once - the suite controls how many sessions are run */1);
357 		// sanity check to make sure we are running with good data
358 		assertEquals("missing content types", TOTAL_NUMBER_OF_ELEMENTS, countTestContentTypes(Platform.getContentTypeManager().getAllContentTypes()));
359 	}
360 
361 	/** Tests how much the size of the catalog affects the performance of content type matching by name */
testNameMatching()362 	public void testNameMatching() {
363 		// warm up preference service
364 		loadPreferences();
365 		// warm up content type registry
366 		final IContentTypeManager manager = loadContentTypeManager();
367 		loadDescribers();
368 		loadChildren();
369 		new PerformanceTestRunner() {
370 			@Override
371 			protected void test() {
372 				IContentType[] associated = manager.findContentTypesFor("foo.txt");
373 				// we know at least the etxt content type should be here
374 				assertTrue("2.0", associated.length >= 1);
375 				// and it is supposed to be the first one (since it is at the root)
376 				assertEquals("2.1", IContentTypeManager.CT_TEXT, associated[0].getId());
377 			}
378 		}.run(this, 10, 200000);
379 	}
380 }