blob: 4c16bc2faa42f3529a4694d5ab416c6110846bb9 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009-2010 University of Illinois at Urbana-Champaign 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:
* UIUC - Initial API and implementation
*******************************************************************************/
package org.eclipse.photran.internal.tests;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.Collections;
import java.util.TreeMap;
import junit.framework.Test;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Plugin;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.Refactoring;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.photran.core.IFortranAST;
import org.eclipse.photran.internal.core.lexer.Token;
import org.eclipse.photran.internal.core.refactoring.infrastructure.FortranEditorRefactoring;
import org.eclipse.photran.internal.core.refactoring.infrastructure.FortranResourceRefactoring;
import org.eclipse.photran.internal.core.util.LineCol;
import org.eclipse.photran.internal.core.vpg.PhotranVPG;
import org.eclipse.rephraserengine.core.vpg.refactoring.VPGResourceRefactoring;
import org.eclipse.rephraserengine.testing.junit3.GeneralTestSuiteFromMarkers;
/**
* A test suite constructed by importing files from a directory in the source tree, searching its
* files for <i>markers</i> (comma-separated lines starting with <tt>!&lt;&lt;&lt;&lt;&lt;</tt>),
* and adding one test case to the suite for each marker.
* <p>
* Markers are expected to have one of the following forms:
* <ol>
* <li> <tt>!&lt;&lt;&lt;&lt;&lt; line, col, ...</tt>
* <li> <tt>!&lt;&lt;&lt;&lt;&lt; fromLine, fromCol, toLine, toCol, ...</tt>
* </ol>
* That is, the first two fields in each marker are expected to be a line and column number; the
* text selection passed to the refactoring will be the offset of that line and column. The third
* fourth fields may also be a line and column number; then, the selection passed to the refactoring
* will extend from the first line/column to the second line/column.
* <p>
* The line and column numbers may be followed by an arbitrary number of fields that contain data
* specific to the refactoring being invoked. Many refactorings don't require any additional data;
* the Extract Local Variable test suite uses one field for the new variable declaration; the Add
* ONLY to USE Statement test suite uses these fields to list the module entities to add; etc.
* <p>
* The final field must be either &quot;pass&quot;, &quot;fail-initial&quot;,
* or &quot;fail-final&quot;, indicating whether the refactoring should succeed,
* fail its initial precondition check, or fail its final precondition check.
* <p>
* If the refactoring is expected to succeed, the Fortran program will be compiled and run before
* and after the refactoring in order to ensure that the refactoring actually preserved behavior.
*
* @author Jeff Overbey
*/
public abstract class PhotranRefactoringTestSuiteFromMarkers<R extends VPGResourceRefactoring<IFortranAST, Token, PhotranVPG>>
extends GeneralTestSuiteFromMarkers
{
/** Text of the last marker field when a refactoring is pass all precondition checks */
private static final String PASS = "pass";
/** Text of the last marker field when a refactoring is expected to fail initial precondition check */
private static final String FAIL_INITIAL = "fail-initial";
/** Text of the last marker field when a refactoring is expected to fail final precondition check */
private static final String FAIL_FINAL = "fail-final";
/** The marker to search for */
protected static final String MARKER = "!<<<<<";
/** Filter that determines which files will be imported into the runtime workspace */
protected static final FilenameFilter FORTRAN_FILE_FILTER = new FilenameFilter()
{
public boolean accept(File dir, String filename)
{
return !filename.endsWith(".result")
&& !filename.equalsIgnoreCase("CVS")
&& !filename.equalsIgnoreCase(".svn");
}
};
/** The activator class that will be used to load test files from the source tree */
protected Plugin activator;
/** The refactoring under test */
protected Class<R> refactoringClass;
protected PhotranRefactoringTestSuiteFromMarkers(Plugin activator, String descriptionPrefix, String directory, Class<R> clazz) throws Exception
{
super(descriptionPrefix, MARKER, new File(directory), FORTRAN_FILE_FILTER,
activator, // initializationData[0]
clazz); // initializationData[1]
}
@SuppressWarnings("unchecked")
@Override protected void initialize(Object... initializationData)
{
this.activator = (Plugin)initializationData[0];
this.refactoringClass = (Class<R>)initializationData[1];
}
@Override protected Test createTestFor(File fileContainingMarker, int markerOffset, String markerText) throws Exception
{
return new IndividualRefactoringTestCase(activator, description, fileContainingMarker, markerText);
}
/**
* This method is invoked to instantiate the refactoring class. Override if necessary.
*/
protected R createRefactoring() throws InstantiationException, IllegalAccessException
{
return refactoringClass.newInstance();
}
/**
* This method is invoked after the refactoring is created ({@link #createRefactoring()}) but
* before {@link Refactoring#checkInitialConditions(org.eclipse.core.runtime.IProgressMonitor)}
* is invoked. Override if necessary.
*
* @return true iff the refactoring is expected to pass initial precondition checking
*/
protected boolean initializeRefactoring(R refactoring, IFile file, TextSelection selection, String[] markerText)
{
if (refactoring instanceof FortranEditorRefactoring)
((FortranEditorRefactoring)refactoring).initialize(file, selection);
else if (refactoring instanceof FortranResourceRefactoring)
((FortranResourceRefactoring)refactoring).initialize(Collections.singletonList(file));
else
throw new IllegalStateException();
if (lastMarkerField(markerText).equals(PASS) || lastMarkerField(markerText).equals(FAIL_FINAL))
return true;
else if (lastMarkerField(markerText).equals(FAIL_INITIAL))
return false;
else
throw new IllegalStateException();
}
/**
* This method is invoked after initial preconditions have been checked
* ({@link Refactoring#checkInitialConditions(org.eclipse.core.runtime.IProgressMonitor)})
* but before final preconditions are checked
* ({@link Refactoring#checkFinalConditions(org.eclipse.core.runtime.IProgressMonitor)}).
* Override if necessary to supply user input.
*
* @return true iff the refactoring is expected to pass final precondition checking
*/
protected boolean configureRefactoring(R refactoring, IFile file, TextSelection selection, String[] markerText)
{
if (lastMarkerField(markerText).equals(PASS))
return true;
else if (lastMarkerField(markerText).equals(FAIL_FINAL))
return false;
else
throw new IllegalStateException();
}
private String lastMarkerField(String[] markerText)
{
return markerText[markerText.length-1];
}
public class IndividualRefactoringTestCase extends PhotranWorkspaceTestCase
{
private Plugin activator;
private String description;
private File fileContainingMarker;
private String markerText;
private TreeMap<String, IFile> files = new TreeMap<String, IFile>();
public IndividualRefactoringTestCase(Plugin activator, String description, File fileContainingMarker, String markerText) throws Exception
{
this.activator = activator;
this.description = description;
this.fileContainingMarker = fileContainingMarker;
this.markerText = markerText;
this.setName("test");
}
public void test() throws Exception
{
IFile fileContainingMarker = importFiles();
R refactoring = createRefactoring();
String[] markerFields = parseMarker();
TextSelection selection = determineSelection(markerFields);
appendFilenameToDescription(markerFields);
RefactoringStatus status =
checkInitialConditions(refactoring,
initializeRefactoring(refactoring, fileContainingMarker, selection, markerFields));
if (!status.hasFatalError())
{
String before = shouldCompile(fileContainingMarker) ? compileAndRunFortranProgram() : "";
status = checkFinalConditions(refactoring,
configureRefactoring(refactoring, fileContainingMarker, selection, markerFields));
if (!status.hasFatalError())
{
performChange(refactoring);
if (!status.hasError() && shouldCompile(fileContainingMarker))
assertEquals(before, compileAndRunFortranProgram());
compareAgainstResultFile();
}
}
}
private IFile importFiles() throws Exception
{
importFiles(fileContainingMarker.getParent());
return files.get(fileContainingMarker.getName());
}
private String compileAndRunFortranProgram() throws Exception
{
project.refreshLocal(IResource.DEPTH_INFINITE, new NullProgressMonitor());
System.out.println("Compiling and running Fortran program");
return compileAndRunFortranProgram(files.keySet().toArray(new String[files.size()]));
}
private String[] parseMarker()
{
String[] markerStrings = markerText.split(",");
for (int i = 0; i < markerStrings.length; i++)
markerStrings[i] = markerStrings[i].trim();
return markerStrings;
}
private TextSelection determineSelection(String[] markerStrings) throws IOException, CoreException
{
assertTrue(markerStrings.length >= 2);
int fromLine = Integer.parseInt(markerStrings[0]);
int fromCol = Integer.parseInt(markerStrings[1]);
int fromOffset = getLineColOffset(fileContainingMarker.getName(), new LineCol(fromLine, fromCol));
int length = 0;
if (markerStrings.length >= 4 && isInteger(markerStrings[2]) && isInteger(markerStrings[3]))
{
int toLine = Integer.parseInt(markerStrings[2]);
int toCol = Integer.parseInt(markerStrings[3]);
int toOffset = getLineColOffset(fileContainingMarker.getName(), new LineCol(toLine, toCol));
length = toOffset - fromOffset;
}
TextSelection selection = new TextSelection(createDocument(), fromOffset, length);
return selection;
}
private IDocument createDocument() throws IOException, CoreException
{
return new Document(readWorkspaceFile(fileContainingMarker.getName()));
}
private void appendFilenameToDescription(String[] markerStrings)
{
description += " (" + fileContainingMarker.getName() + " " + Arrays.toString(markerStrings) + ")";
}
private RefactoringStatus checkInitialConditions(R refactoring, boolean shouldSucceed)
{
RefactoringStatus status = refactoring.checkInitialConditions(new NullProgressMonitor());
if (shouldSucceed)
assertTrue(description + " failed initial precondition check: " + status.toString(), !status.hasError());
else
assertTrue(description + " should have failed initial precondition check but did not: " + status.toString(), status.hasError());
return status;
}
private RefactoringStatus checkFinalConditions(R refactoring, boolean shouldSucceed)
{
RefactoringStatus status;
status = refactoring.checkFinalConditions(new NullProgressMonitor());
if (shouldSucceed)
assertTrue(description + " failed final precondition check: " + status.toString(), !status.hasError());
else
assertTrue(description + " should have failed final precondition check but did not: " + status.toString(), status.hasError());
return status;
}
private void performChange(R refactoring) throws CoreException
{
IProgressMonitor pm = new NullProgressMonitor();
Change change = refactoring.createChange(pm);
assertNotNull(description + " returned null Change object", change);
assertTrue(description + " returned invalid Change object", change.isValid(pm).isOK());
change.perform(pm);
}
private void compareAgainstResultFile() throws IOException, URISyntaxException, CoreException
{
for (String filename : files.keySet())
{
if (resultFileFor(filename).exists())
{
String expected = readTestFile(activator, fileContainingMarker.getParent(), filename + ".result").replaceAll("\\r", "");
String actual = readWorkspaceFile(filename).replaceAll("\\r", "");
assertEquals(expected, actual); // actual refactored file
}
}
}
private File resultFileFor(String filename)
{
return new File(fileContainingMarker.getParent()
+ File.separator
+ filename
+ ".result");
}
/**
* @return true iff {@link Integer#parseInt(String)} can successfully parse the given
* string can be parsed as an integer
*/
private boolean isInteger(String string)
{
try
{
Integer.parseInt(string);
return true;
}
catch (NumberFormatException e)
{
return false;
}
}
private void importFiles(String subdir) throws Exception
{
PhotranVPG.getDatabase().clearDatabase();
files = new TreeMap<String, IFile>();
for (File file : new File(subdir).listFiles())
{
String filename = file.getName();
if (!filename.endsWith(".result")
&& !filename.equalsIgnoreCase("CVS")
&& !filename.equalsIgnoreCase(".svn"))
{
IFile thisFile = importFile(activator, subdir, filename);
files.put(filename, thisFile);
}
}
IProgressMonitor pm = new NullProgressMonitor();
project.refreshLocal(IResource.DEPTH_INFINITE, pm);
PhotranVPG.getInstance().ensureVPGIsUpToDate(pm);
}
}
/**
* @return true iff the Fortran program should be compiled and run
*/
protected boolean shouldCompile(IFile fileContainingMarker)
{
return true;
}
}