/******************************************************************************* * Copyright (c) 2000, 2003 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Common Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/cpl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.team.tests.ccvs.ui.benchmark; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.zip.ZipException; import java.util.zip.ZipFile; import junit.framework.Assert; import org.eclipse.compare.structuremergeviewer.*; import org.eclipse.core.resources.*; import org.eclipse.core.runtime.*; import org.eclipse.team.internal.ccvs.core.CVSStatus; import org.eclipse.ui.wizards.datatransfer.ImportOperation; import org.eclipse.ui.wizards.datatransfer.ZipFileStructureProvider; /** * Provides helpers for: * * * Note: This class is referenced from the VCM 1.0 performance tests. */ public class BenchmarkUtils { /*** RESOURCE MANIPULATION SUPPORT ***/ /** * Gets a handle for a project of a given name. * @param name the project name * @return the project handle */ public static IProject getProject(String name) throws CoreException { return ResourcesPlugin.getWorkspace().getRoot().getProject(name); } /** * Creates a new project. * @param name the project name * @return the project handle */ public static IProject createProject(String name) throws CoreException { IProject project = getProject(name); if (!project.exists()) project.create(null); if (!project.isOpen()) project.open(null); return project; } /** * Deletes a project. * @param project the project */ public static void deleteProject(IProject project) throws CoreException { project.delete(false /*force*/, null); } /** * Deletes a file and prunes empty containing folders. * @param file the file to delete */ public static void deleteFileAndPrune(IFile file) throws CoreException { file.delete(false /*force*/, null); IContainer container = file.getParent(); while (container != null && container instanceof IFolder && isFolderEmpty((IFolder) container)) { deleteFolder((IFolder) container); container = container.getParent(); } } /** * Deletes a folder. */ public static void deleteFolder(IFolder folder) throws CoreException { try { folder.delete(false /*force*/, null); } catch (CoreException e) { IStatus status = e.getStatus(); // ignore errors caused by attempting to delete folders that CVS needs to have around if (findStatusByCode(status, CVSStatus.FOLDER_NEEDED_FOR_FILE_DELETIONS) == null) { throw e; } } } /** * Finds an IStatus instance in a multi-status by status code. */ public static IStatus findStatusByCode(IStatus status, int code) { if (status.getCode() == code) return status; IStatus[] children = status.getChildren(); for (int i = 0; i < children.length; i++) { IStatus found = findStatusByCode(children[i], code); if (found != null) return found; } return null; } /** * Creates a uniquely named project. * @param prefix a string prepended to the generated name * @return the new project */ public static IProject createUniqueProject(String prefix) throws CoreException { return createProject(makeUniqueName(null, prefix, null)); } /** * Creates a uniquely named file in the parent folder or project with random contents. * @param gen the sequence generator * @param parent the parent IFolder or IProject for the new file * @param meanSize the mean size of file to create (in bytes) * @param variance 69% of files with be within this amount of the mean * @param probBinary the probability of a new file being binary as a percentage * @return the new file */ public static IFile createUniqueFile(SequenceGenerator gen, IContainer parent, int meanSize, int variance, int probBinary) throws IOException, CoreException { int fileSize; do { fileSize = (int) Math.abs(gen.nextGaussian() * variance + meanSize); } while (fileSize > meanSize + variance * 4); // avoid huge files ByteArrayOutputStream os = new ByteArrayOutputStream(); String fileName; if (gen.nextInt(100) < probBinary) { fileName = makeUniqueName(gen, "file", "class"); // binary writeRandomBytes(gen, os, fileSize); } else { fileName = makeUniqueName(gen, "file", "txt"); // text writeRandomText(gen, os, fileSize); } IFile file = parent.getFile(new Path(fileName)); file.create(new ByteArrayInputStream(os.toByteArray()), true, null); os.close(); return file; } /** * Creates a uniquely named folder in the parent folder. * @param gen the sequence generator * @param parent the parent IFolder or IProject for the new folder * @return the new folder */ public static IFolder createUniqueFolder(SequenceGenerator gen, IContainer parent) throws CoreException { IFolder folder = parent.getFolder(new Path(BenchmarkUtils.makeUniqueName(gen, "folder", null))); folder.create(false /*force*/, true /*local*/, null); return folder; } /** * Renames a resource. * The resource handle becomes invalid. * @param resource the existing resource * @param newName the new name for the resource */ public static void renameResource(IResource resource, String newName) throws CoreException { switch (resource.getType()) { case IResource.PROJECT: { IProject project = (IProject) resource; IProjectDescription desc = project.getDescription(); desc.setName(newName); project.move(desc, false /*force*/, true /*keepHistory*/, null); } break; case IResource.FOLDER: { try { resource.move(new Path(newName), false /*force*/, null); } catch (CoreException e) { IStatus status = e.getStatus(); // ignore errors caused by attempting to delete folders that CVS needs to have around if (findStatusByCode(status, CVSStatus.FOLDER_NEEDED_FOR_FILE_DELETIONS) == null) { throw e; } } } break; default: resource.move(new Path(newName), false /*force*/, null); break; } } /** * Modified a resource. * @param gen the sequence generator * @param file the file to modify */ public static void modifyFile(SequenceGenerator gen, IFile file) throws IOException, CoreException { ByteArrayOutputStream os = new ByteArrayOutputStream(); try { InputStream is = file.getContents(true); try { byte[] buffer = new byte[8192]; int rsize; boolean changed = false; while ((rsize = is.read(buffer)) != -1) { double gaussian; do { gaussian = gen.nextGaussian() * 0.5; // large changes are less likely than small ones } while (gaussian > 1.0 || gaussian < -1.0); int changeSize = (int) (gaussian * rsize); changed = changed || changeSize != 0; os.write(buffer, 0, changeSize < 0 ? - changeSize : rsize); // shrink file writeRandomText(gen, os, changeSize); // enlarge file } if (! changed) os.write('!'); // make sure we actually did change the file file.setContents(new ByteArrayInputStream(os.toByteArray()), false /*force*/, true /*keepHistory*/, null); } finally { is.close(); } } finally { os.close(); } } /** * Creates a unique name. * Ensures that a deterministic sequence of names is generated for all files * and folders within a project, though not across sessions. * * @param gen the generator, or null if this name is to be globally unique * @param prefix a string prepended to the generated name * @param extension the file extension not including the period, null if none * @return the new name */ public static String makeUniqueName(SequenceGenerator gen, String prefix, String extension) throws CoreException { StringBuffer name = new StringBuffer(prefix); name.append('-'); if (gen == null) { name.append(SequenceGenerator.nextGloballyUniqueLong()); } else { name.append(gen.nextUniqueInt()); } if (extension != null) { name.append('.'); name.append(extension); } return name.toString(); } /** * Imports a .zip file into a container's root folder. * @param container the container * @param file the path of the .zip file */ public static void importZip(IContainer container, File file) throws IOException, ZipException, InterruptedException, InvocationTargetException { ZipFile zipFile = new ZipFile(file); ZipFileStructureProvider provider = new ZipFileStructureProvider(zipFile); ImportOperation importOperation = new ImportOperation(container.getFullPath(), provider.getRoot(), provider, null); importOperation.setOverwriteResources(true); // don't ask importOperation.run(new NullProgressMonitor()); Assert.assertTrue(importOperation.getStatus().isOK()); } /** * Writes random text to an output stream. * @param gen the sequence generator */ public static void writeRandomText(SequenceGenerator gen, OutputStream os, int count) throws IOException { while (count-- > 0) { int c = gen.nextInt(99); os.write((c >= 95) ? '\n' : c + ' '); } } /** * Writes random bytes to an output stream. * @param gen the sequence generator */ public static void writeRandomBytes(SequenceGenerator gen, OutputStream os, int count) throws IOException { while (count-- > 0) { os.write(gen.nextInt(256)); } } /** * Creates a random folder deeply below the root folder. * @param gen the sequence generator * @param root the root IFolder or IProject for the operation * @return the new folder */ public static IFolder createRandomDeepFolder(SequenceGenerator gen, IContainer root) throws CoreException { IContainer container = pickRandomDeepContainer(gen, root); for (;;) { IFolder folder = createUniqueFolder(gen, container); container = folder; // 12.5% chance of creating a nested folder if (gen.nextInt(8) != 0) return folder; } } /** * Creates several random files deeply below the root folder. * @param gen the sequence generator * @param root the root IFolder or IProject for the operation * @param count the number of files to create * @param meanSize the mean size of file to create (in bytes) * @param probBinary the probability of a new file being binary as a percentage */ public static void createRandomDeepFiles(SequenceGenerator gen, IContainer root, int count, int meanSize, int variance, int probBinary) throws IOException, CoreException { while (count-- > 0) { createUniqueFile(gen, pickRandomDeepContainer(gen, root), meanSize, variance, probBinary); } } /** * Deletes several random files deeply below the root folder. * @param gen the sequence generator * @param root the root IFolder or IProject for the operation * @param count the number of files to delete */ public static void deleteRandomDeepFiles(SequenceGenerator gen, IContainer root, int count) throws CoreException { while (count-- > 0) { IFile file = pickRandomDeepFile(gen, root); if (file == null) break; deleteFileAndPrune(file); } } /** * Modifies several random files deeply below the root folder. * @param gen the sequence generator * @param root the root IFolder or IProject for the operation * @param count the number of files to modify */ public static void modifyRandomDeepFiles(SequenceGenerator gen, IContainer root, int count) throws IOException, CoreException { // perhaps we can add a parameter for the "magnitude" of the change while (count-- > 0) { IFile file = pickRandomDeepFile(gen, root); if (file == null) break; modifyFile(gen, file); } } /** * Touches several random files deeply below the root folder. * @param gen the sequence generator * @param root the root IFolder or IProject for the operation * @param count the number of files to touch */ public static void touchRandomDeepFiles(SequenceGenerator gen, IContainer root, int count) throws CoreException { while (count-- > 0) { IFile file = pickRandomDeepFile(gen, root); if (file == null) break; file.touch(null); } } /** * Renames several random files deeply below the root folder. * @param gen the sequence generator * @param root the root IFolder or IProject for the operation * @param count the number of files to touch */ public static void renameRandomDeepFiles(SequenceGenerator gen, IContainer root, int count) throws CoreException { IProject project = root.getProject(); while (count-- > 0) { IFile file = pickRandomDeepFile(gen, root); if (file == null) break; renameResource(file, makeUniqueName(gen, "file", file.getFileExtension())); } } /** * Picks a random file from the parent folder or project. * @param gen the sequence generator * @param parent the parent IFolder or IProject for the operation * @return the file that was chosen, or null if no suitable files */ public static IFile pickRandomFile(SequenceGenerator gen, IContainer parent) throws CoreException { IResource[] members = filterResources(parent.members()); for (int size = members.length; size != 0; --size) { int elem = gen.nextInt(size); if (members[elem] instanceof IFile) return (IFile) members[elem]; System.arraycopy(members, elem + 1, members, elem, size - elem - 1); } return null; } /** * Picks a random folder from the parent folder or project. * @param gen the sequence generator * @param parent the parent IFolder or IProject for the operation * @return the folder, or null if no suitable folders */ public static IFolder pickRandomFolder(SequenceGenerator gen, IContainer parent) throws CoreException { IResource[] members = filterResources(parent.members()); for (int size = members.length; size != 0; --size) { int elem = gen.nextInt(size); if (members[elem] instanceof IFolder) return (IFolder) members[elem]; System.arraycopy(members, elem + 1, members, elem, size - elem - 1); } return null; } /** * Picks a random file deeply from the root folder or project. * @param gen the sequence generator * @param root the root IFolder or IProject for the operation * @return the file that was chosen, or null if no suitable files */ public static IFile pickRandomDeepFile(SequenceGenerator gen, IContainer root) throws CoreException { IResource[] members = filterResources(root.members()); for (int size = members.length; size != 0; --size) { int elem = gen.nextInt(size); IResource resource = members[elem]; if (resource instanceof IFile) return (IFile) resource; if (resource instanceof IFolder) { IFile file = pickRandomDeepFile(gen, (IFolder) resource); if (file != null) return file; } System.arraycopy(members, elem + 1, members, elem, size - elem - 1); } return null; } /** * Picks a random folder deeply from the root folder or project. * May pick the project's root container. * @param gen the sequence generator * @param root the root IFolder or IProject for the operation * @return the container that was chosen, never null */ public static IContainer pickRandomDeepContainer(SequenceGenerator gen, IContainer root) throws CoreException { if (gen.nextInt(6) == 0) { IResource[] members = filterResources(root.members()); for (int size = members.length; size != 0; --size) { int elem = gen.nextInt(size); IResource resource = members[elem]; if (resource instanceof IFolder) { return pickRandomDeepContainer(gen, (IFolder) resource); } System.arraycopy(members, elem + 1, members, elem, size - elem - 1); } } Assert.assertTrue(isValidContainer(root)); return root; } /** * Returns true if the folder does not contain any real files. */ public static boolean isFolderEmpty(IFolder folder) throws CoreException { IResource[] members = folder.members(); for (int i = 0; i < members.length; ++i) { if (isValidFile(members[i]) || isValidFolder(members[i])) return false; } return true; } /** * Returns true iff file is a valid IFile (that should not be ignored). */ public static boolean isValidFile(IResource file) throws CoreException { String name = file.getName(); return file instanceof IFile && ! file.isPhantom() && ! name.equals(".classpath") && ! name.equals(".project") && ! name.equals(".vcm_meta"); } /** * Returns true iff folder is a valid IFolder (that should not be ignored). */ public static boolean isValidFolder(IResource folder) throws CoreException { String name = folder.getName(); return folder instanceof IFolder && ! folder.isPhantom() && ! name.equals("CVS") && ! name.equals("bin"); } /** * Returns true iff container is a valid IFolder or IProject (that should not be ignored). */ public static boolean isValidContainer(IResource container) throws CoreException { return container instanceof IProject || isValidFolder(container); } /** * Returns true iff resource is a valid IFile, IFolder or IProject (that should not be ignored). */ public static boolean isValidResource(IResource resource) throws CoreException { return isValidFile(resource) || isValidContainer(resource); } /** * Filters and sorts an array of resources to ensure deterministic behaviour across * sessions. The general idea is to guarantee that given a known sequence of * pseudo-random numbers, we will always pick the same sequence of files and * folders each time we repeat the test. */ public static IResource[] filterResources(IResource[] resources) throws CoreException { List list = new ArrayList(resources.length); for (int i = 0; i < resources.length; ++i) { if (isValidResource(resources[i])) list.add(resources[i]); } if (list.size() != resources.length) { resources = (IResource[]) list.toArray(new IResource[list.size()]); } Arrays.sort(resources, new Comparator() { public int compare(Object a, Object b) { return ((IResource) a).getName().compareTo(((IResource) b).getName()); } }); return resources; } /*** DIFF SUPPORT ***/ public static boolean isEmpty(IDiffContainer node) { if (node == null) return true; if (node.getKind() != 0) return false; IDiffElement[] children = node.getChildren(); for (int i = 0; i < children.length; i++) { if (!isEmpty(children[i])) return false; } return true; } public static boolean isEmpty(IDiffElement element) { if (element == null) return true; if (element.getKind() != 0) return false; if (element instanceof IDiffContainer) { IDiffElement[] children = ((DiffNode)element).getChildren(); for (int i = 0; i < children.length; i++) { if (!isEmpty(children[i])) return false; } } return true; } }