// =====================================================================
//
// Copyright (C) 2012 - 2016, Philip Graf
//
// 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
//
// =====================================================================
package ch.acanda.eclipse.pmd.java.resolution;
import static java.text.MessageFormat.format;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.core.resources.IMarker;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Position;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.TextEdit;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import ch.acanda.eclipse.pmd.java.resolution.QuickFixTestData.TestParameters;
import ch.acanda.eclipse.pmd.marker.PMDMarker;
import ch.acanda.eclipse.pmd.marker.WrappingPMDMarker;
import ch.acanda.eclipse.pmd.ui.util.PMDPluginImages;
/**
* Base class for testing quick fix tests based on {@link ASTQuickFix}. An extending class must provide a static method
* with the annotation {@link org.junit.runners.Parameterized.Parameters} that returns the parameters for the test case,
* e.g:
*
* <pre>
* @Parameters
* public static Collection<Object[]> getTestData() {
* return createTestData(ExtendsObjectQuickFixTest.class.getResourceAsStream("ExtendsObject.xml"));
* }
* </pre>
*
* The easiest way to implement this method is to use {@link QuickFixTestData#createTestData(InputStream)} and provide
* an {@code InputStream} to an XML file containing all the test data. See {@link QuickFixTestData} for the format of
* the XML file.
*
* See {@link ch.acanda.eclipse.pmd.java.resolution.basic.ExtendsObjectQuickFixTest ExtendsObjectQuickFixTest} for a
* complete example.
*
* @author Philip Graf
* @param <T> The type of the quick fix.
*/
@RunWith(value = Parameterized.class)
@SuppressWarnings({ "PMD.CommentSize", "PMD.AbstractClassWithoutAbstractMethod" })
public abstract class ASTQuickFixTestCase<T extends ASTQuickFix<? extends ASTNode>> {
private final TestParameters params;
public ASTQuickFixTestCase(final TestParameters parameters) {
params = parameters;
}
@SuppressWarnings("unchecked")
private ASTQuickFix<ASTNode> getQuickFix() {
try {
final Type typeArgument = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
final Class<T> quickFixClass = (Class<T>) typeArgument;
final IMarker marker = mock(IMarker.class);
when(marker.getAttribute(eq("ruleName"), isA(String.class))).thenReturn(params.rulename.orNull());
final String markerText = params.source.substring(params.offset, params.offset + params.length);
if (!markerText.contains("\n")) {
when(marker.getAttribute(eq("markerText"), isA(String.class))).thenReturn(markerText);
}
return (ASTQuickFix<ASTNode>) quickFixClass.getConstructor(PMDMarker.class).newInstance(new WrappingPMDMarker(marker));
} catch (SecurityException | ReflectiveOperationException e) {
throw new IllegalArgumentException(e);
}
}
public static List<Object[]> createTestData(final InputStream testCase) {
return Lists.transform(QuickFixTestData.createTestData(testCase), params -> new Object[] { params });
}
@Test
public void apply() throws MalformedTreeException, BadLocationException {
final ASTQuickFix<ASTNode> quickFix = getQuickFix();
final org.eclipse.jface.text.Document document = new org.eclipse.jface.text.Document(params.source);
final CompilationUnit ast = createAST(document, quickFix);
final ASTNode node = findNode(params, ast, quickFix);
quickFix.apply(node);
final String actual = rewriteAST(document, ast);
assertEquals("Result of applying the quick fix " + quickFix.getClass().getSimpleName() + " to the test " + params.name,
params.expectedSource, actual);
}
private ASTNode findNode(final TestParameters params, final CompilationUnit ast, final ASTQuickFix<ASTNode> quickFix) {
final Class<? extends ASTNode> nodeType = quickFix.getNodeType();
final NodeFinder<CompilationUnit, ASTNode> finder = quickFix.getNodeFinder(new Position(params.offset, params.length));
final Optional<ASTNode> node = finder.findNode(ast);
assertTrue("Couldn't find node of type " + nodeType.getSimpleName() + "."
+ " Check the position of the marker in test " + params.name + ".", node.isPresent());
return node.get();
}
private CompilationUnit createAST(final org.eclipse.jface.text.Document document, final ASTQuickFix<ASTNode> quickFix) {
final ASTParser astParser = ASTParser.newParser(AST.JLS4);
astParser.setSource(document.get().toCharArray());
astParser.setKind(ASTParser.K_COMPILATION_UNIT);
astParser.setResolveBindings(quickFix.needsTypeResolution());
astParser.setEnvironment(new String[0], new String[0], new String[0], true);
final String name = last(params.pmdReferenceId.or("QuickFixTest").split("/"));
astParser.setUnitName(format("{0}.java", name));
final String version = last(params.language.or("java 1.7").split("\\s+"));
astParser.setCompilerOptions(ImmutableMap.<String, String>builder()
.put(JavaCore.COMPILER_SOURCE, version)
.put(JavaCore.COMPILER_COMPLIANCE, version)
.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, version)
.build());
final CompilationUnit ast = (CompilationUnit) astParser.createAST(null);
ast.recordModifications();
return ast;
}
private String last(final String... strings) {
return strings[strings.length - 1];
}
private String rewriteAST(final org.eclipse.jface.text.Document document, final CompilationUnit ast) throws BadLocationException {
final TextEdit edit = ast.rewrite(document, getRewriteOptions());
edit.apply(document);
return document.get();
}
private Map<String, String> getRewriteOptions() {
final Map<String, String> options = new HashMap<>();
options.put(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR, JavaCore.SPACE);
options.put(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE, "4");
options.put(DefaultCodeFormatterConstants.FORMATTER_INDENT_SWITCHSTATEMENTS_COMPARE_TO_SWITCH, DefaultCodeFormatterConstants.TRUE);
options.put(DefaultCodeFormatterConstants.FORMATTER_INDENT_SWITCHSTATEMENTS_COMPARE_TO_CASES, DefaultCodeFormatterConstants.TRUE);
options.put(DefaultCodeFormatterConstants.FORMATTER_INDENT_BREAKS_COMPARE_TO_CASES, DefaultCodeFormatterConstants.TRUE);
options.put(DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_BEFORE_ASSIGNMENT_OPERATOR, JavaCore.DO_NOT_INSERT);
options.put(DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_AFTER_ASSIGNMENT_OPERATOR, JavaCore.DO_NOT_INSERT);
options.put(DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_AFTER_COMMA_IN_ANNOTATION, JavaCore.INSERT);
options.put(DefaultCodeFormatterConstants.FORMATTER_INSERT_SPACE_AFTER_COMMA_IN_ARRAY_INITIALIZER, JavaCore.DO_NOT_INSERT);
options.put(DefaultCodeFormatterConstants.FORMATTER_INSERT_NEW_LINE_AFTER_ANNOTATION_ON_LOCAL_VARIABLE, JavaCore.DO_NOT_INSERT);
options.put(DefaultCodeFormatterConstants.FORMATTER_INSERT_NEW_LINE_AFTER_ANNOTATION_ON_METHOD, JavaCore.DO_NOT_INSERT);
options.put(DefaultCodeFormatterConstants.FORMATTER_INSERT_NEW_LINE_AFTER_ANNOTATION_ON_PACKAGE, JavaCore.DO_NOT_INSERT);
options.put(DefaultCodeFormatterConstants.FORMATTER_INSERT_NEW_LINE_AFTER_ANNOTATION_ON_PARAMETER, JavaCore.DO_NOT_INSERT);
options.put(DefaultCodeFormatterConstants.FORMATTER_INSERT_NEW_LINE_AFTER_ANNOTATION_ON_TYPE, JavaCore.DO_NOT_INSERT);
return options;
}
@Test
public void getImage() throws IllegalAccessException, NoSuchFieldException, SecurityException {
final ImageDescriptor imageDescriptor = getQuickFix().getImageDescriptor();
if (params.expectedImage.isPresent()) {
final Field field = PMDPluginImages.class.getDeclaredField(params.expectedImage.get());
assertEquals("Quick fix image descriptor in test " + params.name, field.get(null), imageDescriptor);
} else {
assertNotNull("Quick fix image descriptor must not be null (test " + params.name + ")", imageDescriptor);
}
}
@Test
public void getLabel() {
final String label = getQuickFix().getLabel();
if (params.expectedLabel.isPresent()) {
assertEquals("Quick fix label in test " + params.name, params.expectedLabel.get(), label);
} else {
assertNotNull("Quick fix label must not be null (test " + params.name + ")", label);
}
}
@Test
public void getDescription() {
final String description = getQuickFix().getDescription();
if (params.expectedDescription.isPresent()) {
assertEquals("Quick fix description in test " + params.name, params.expectedDescription.get(), description);
} else {
assertNotNull("Quick fix description must not be null (test " + params.name + ")", description);
}
}
}