Add some tests for quickfixes

This commit is contained in:
Kris De Volder
2017-04-12 14:53:48 -07:00
parent 2549956562
commit 87a7026215
8 changed files with 235 additions and 5 deletions

View File

@@ -16,6 +16,15 @@ public class QuickfixResolveParams {
private Object params;
public QuickfixResolveParams(String type, Object params) {
super();
this.type = type;
this.params = params;
}
public QuickfixResolveParams() {
}
public String getType() {
return type;
}

View File

@@ -18,6 +18,21 @@ import java.util.Collection;
*/
public class CollectionUtil {
/**
* Get some element of the collection (will be the first one
* found by its iterator, or null if the collection is empty).
* <p>
* Note that unless the collection is ordered, or has at most
* one element, then it may be unpredictable which element
* you will get.
*/
public static <E> E getAny(Collection<E> elements) {
for (E e : elements) {
return e;
}
return null;
}
public static <E> boolean hasElements(Collection<E> c) {
return c!=null && !c.isEmpty();
}

View File

@@ -22,6 +22,7 @@ import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemTy
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
import org.springframework.ide.vscode.commons.util.CollectionUtil;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.yaml.ast.NodeUtil;
import org.springframework.ide.vscode.commons.yaml.path.YamlPath;
@@ -140,6 +141,9 @@ public class YamlSchemaProblems {
.map(YamlPathSegment::encode)
.collect(Collectors.toList());
String fixTitle = missingProps.size()==1
? "Add property '"+CollectionUtil.getAny(missingProps)+"'"
: "Add properties: "+missingProps;
QuickfixData<MissingPropertiesData> fix = new QuickfixData<MissingPropertiesData>(
quickfixType,
new MissingPropertiesData(
@@ -147,7 +151,7 @@ public class YamlSchemaProblems {
segments,
ImmutableList.copyOf(missingProps)
),
"Add properties: "+missingProps
fixTitle
);
return missingProperty(msg, dc.getDocument(), parent, map)

View File

@@ -0,0 +1,44 @@
/*******************************************************************************
* Copyright (c) 2017 Pivotal, Inc.
* 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:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.languageserver.testharness;
import org.eclipse.lsp4j.Command;
/**
* Wrapper for the test harness to refer to and manipulate a
* CodeAction.
*/
public class CodeAction {
private final Command command;
private LanguageServerHarness harness;
public CodeAction(LanguageServerHarness harness, Command command) {
super();
this.harness = harness;
this.command = command;
}
@Override
public String toString() {
return command.toString();
}
public String getLabel() {
return command.getTitle();
}
public void perform() throws Exception {
harness.perform(command);
}
}

View File

@@ -24,6 +24,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import javax.swing.text.BadLocationException;
@@ -42,6 +43,7 @@ import org.eclipse.lsp4j.TextDocumentPositionParams;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.junit.Assert;
import org.springframework.ide.vscode.commons.util.CollectionUtil;
import com.google.common.collect.ImmutableList;
@@ -627,4 +629,22 @@ public class Editor {
return languageId;
}
public List<CodeAction> getCodeActions(Diagnostic problem) throws Exception {
return harness.getCodeActions(document, problem);
}
public CodeAction assertCodeAction(Diagnostic problem) throws Exception {
List<CodeAction> actions = getCodeActions(problem);
assertEquals("Number of codeActions", 1, actions.size());
return actions.get(0);
}
public String getUri() {
return document.getUri();
}
public void assertRawText(String expectedText) throws Exception {
assertEquals(expectedText, getRawText());
}
}

View File

@@ -18,17 +18,23 @@ import static org.junit.Assert.assertNotNull;
import java.io.File;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import org.assertj.core.api.Condition;
import org.eclipse.lsp4j.ClientCapabilities;
import org.eclipse.lsp4j.CodeActionContext;
import org.eclipse.lsp4j.CodeActionParams;
import org.eclipse.lsp4j.Command;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionList;
import org.eclipse.lsp4j.Diagnostic;
@@ -50,13 +56,25 @@ import org.eclipse.lsp4j.TextDocumentItem;
import org.eclipse.lsp4j.TextDocumentPositionParams;
import org.eclipse.lsp4j.TextDocumentSyncKind;
import org.eclipse.lsp4j.TextDocumentSyncOptions;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4j.VersionedTextDocumentIdentifier;
import org.eclipse.lsp4j.WorkspaceEdit;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.services.LanguageClientAware;
import org.springframework.ide.vscode.commons.languageserver.LanguageIds;
import org.springframework.ide.vscode.commons.languageserver.ProgressParams;
import org.springframework.ide.vscode.commons.languageserver.STS4LanguageClient;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits.TextReplace;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixResolveParams;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.util.Assert;
import org.springframework.ide.vscode.commons.util.text.IDocument;
import org.springframework.ide.vscode.commons.util.text.IRegion;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
public class LanguageServerHarness {
@@ -73,6 +91,7 @@ public class LanguageServerHarness {
private Map<String,TextDocumentInfo> documents = new HashMap<>();
private Map<String, PublishDiagnosticsParams> diagnostics = new HashMap<>();
private List<Editor> activeEditors = new ArrayList<>();
public LanguageServerHarness(Callable<? extends SimpleLanguageServer> factory, String defaultLanguageId) {
@@ -340,11 +359,13 @@ public class LanguageServerHarness {
}
public Editor newEditor(String contents) throws Exception {
return new Editor(this, contents, getDefaultLanguageId());
return newEditor(getDefaultLanguageId(), contents);
}
public Editor newEditor(String languageId, String contents) throws Exception {
return new Editor(this, contents, languageId);
public synchronized Editor newEditor(String languageId, String contents) throws Exception {
Editor editor = new Editor(this, contents, languageId);
activeEditors.add(editor);
return editor;
}
public synchronized TextDocumentInfo createWorkingCopy(String contents, String languageId) throws Exception {
@@ -406,4 +427,71 @@ public class LanguageServerHarness {
return server.getTextDocumentService().definition(params).get();
}
public List<CodeAction> getCodeActions(TextDocumentInfo doc, Diagnostic problem) throws Exception {
CodeActionContext context = new CodeActionContext(ImmutableList.of(problem));
List<? extends Command> actions =
server.getTextDocumentService().codeAction(new CodeActionParams(doc.getId(), problem.getRange(), context)).get();
return actions.stream()
.map((command) -> new CodeAction(this, command))
.collect(Collectors.toList());
}
ObjectMapper mapper = new ObjectMapper();
public void perform(Command command) throws Exception {
switch (command.getCommand()) {
case "sts.quickfix":
List<Object> args = command.getArguments();
assertEquals(2, args.size());
//Note convert the value to a 'typeless' Object becaus that is more representative on how it will be
// received when we get it in a real client/server setting (i.e. parsed from json).
Object untypedParams = mapper.convertValue(args.get(1), Object.class);
perform(server.quickfixResolve(new QuickfixResolveParams((String)args.get(0), untypedParams)).get());
return;
default:
throw new IllegalArgumentException("Unknown command: "+command);
}
}
private void perform(WorkspaceEdit workspaceEdit) throws Exception {
Assert.isNull("Versioned WorkspaceEdits not supported", workspaceEdit.getDocumentChanges());
for (Entry<String, List<TextEdit>> entry : workspaceEdit.getChanges().entrySet()) {
String uri = entry.getKey();
TextDocumentInfo document = documents.get(uri);
assertNotNull("Can't apply edits to non-existing document: "+uri, document);
TextDocument workingDocument = new TextDocument(uri, document.getLanguageId());
workingDocument.setText(document.getText());
DocumentEdits edits = new DocumentEdits(workingDocument);
for (TextEdit edit : entry.getValue()) {
Range range = edit.getRange();
edits.replace(document.toOffset(range.getStart()), document.toOffset(range.getEnd()), edit.getNewText());
}
edits.apply(workingDocument);
Editor editor = getOpenEditor(uri);
if (editor!=null) {
editor.setRawText(workingDocument.get());
} else {
changeDocument(uri, workingDocument.get());
}
}
}
private Editor getOpenEditor(String uri) {
List<Editor> editors = getOpenEditors(uri);
if (editors.isEmpty()) {
return null;
} else if (editors.size()>1) {
throw new IllegalStateException("Multiple active editors on the same uri. The harness doesn't handle that yet!");
}
return editors.get(0);
}
private synchronized List<Editor> getOpenEditors(String uri) {
return activeEditors.stream()
.filter((editor) -> uri.equals(editor.getUri()))
.collect(Collectors.toList());
}
}

View File

@@ -24,6 +24,7 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.ide.vscode.commons.languageserver.LanguageIds;
import org.springframework.ide.vscode.commons.util.IOUtil;
import org.springframework.ide.vscode.languageserver.testharness.CodeAction;
import org.springframework.ide.vscode.languageserver.testharness.Editor;
import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness;
@@ -42,6 +43,54 @@ public class ConcourseEditorTest {
harness.intialize(null);
}
@Test public void addSingleRequiredPropertiesQuickfix() throws Exception {
Editor editor = harness.newEditor(
"resources:\n" +
"- name: foo\n" +
" source:\n" +
" username: someone\n" +
"# Confuse"
);
Diagnostic problem = editor.assertProblems("-|'type' is required").get(0);
CodeAction quickfix = editor.assertCodeAction(problem);
assertEquals("Add property 'type'", quickfix.getLabel());
quickfix.perform();
editor.assertRawText(
"resources:\n" +
"- name: foo\n" +
" source:\n" +
" username: someone\n" +
" type: \n" +
"# Confuse"
);
}
@Test public void addMultipleRequiredPropertiesQuickfix() throws Exception {
Editor editor = harness.newEditor(
"resources:\n" +
"- name: foo\n" +
" type: pool\n" +
" source:\n" +
" username: someone\n"
);
Diagnostic problem = editor.assertProblems("source|[branch, pool, uri] are required").get(0);
CodeAction quickfix = editor.assertCodeAction(problem);
assertEquals("Add properties: [branch, pool, uri]", quickfix.getLabel());
quickfix.perform();
editor.assertRawText(
"resources:\n" +
"- name: foo\n" +
" type: pool\n" +
" source:\n" +
" username: someone\n" +
" branch: \n" +
" pool: \n" +
" uri: \n"
);
}
@Test public void testReconcileCatchesParseError() throws Exception {
Editor editor = harness.newEditor(
"somemap: val\n"+

View File

@@ -69,7 +69,6 @@ public class TimeOfDayParserTest {
"2359"
};
for (String string : examples) {
System.out.println(string);
parser.parse(string);
}
}
@@ -78,6 +77,8 @@ public class TimeOfDayParserTest {
public void badExamples() {
does_not_parse("arbirary garbage");
does_not_parse("3:04 PM -0700 extra");
does_not_parse("extra 3:04 PM -0700 extra");
does_not_parse("extra 3:04 PM -0700");
}
private void does_not_parse(String string) {