Files
swift-mirror/unittests/SourceKit/SwiftLang/EditingTest.cpp
Argyrios Kyrtzidis 0cfc56ec04 [SourceKit] If diagnostics are 'stale' for a particular snapshot then ignore them and only return the syntactic parser diagnostics (#10388)
This makes sure that diagnostics returned for a particular state of source buffer are consistent and accurate.
rdar://32769873
2017-06-20 12:26:32 -07:00

225 lines
7.3 KiB
C++

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
#include "SourceKit/Core/Context.h"
#include "SourceKit/Core/LangSupport.h"
#include "SourceKit/Core/NotificationCenter.h"
#include "SourceKit/Support/Concurrency.h"
#include "SourceKit/SwiftLang/Factory.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/TargetSelect.h"
#include "gtest/gtest.h"
#include <mutex>
#include <condition_variable>
using namespace SourceKit;
using namespace llvm;
static StringRef getRuntimeLibPath() {
return sys::path::parent_path(SWIFTLIB_DIR);
}
namespace {
class DiagConsumer : public EditorConsumer {
public:
UIdent DiagStage;
std::vector<DiagnosticEntryInfo> Diags;
private:
void handleRequestError(const char *Description) override {
llvm_unreachable("unexpected error");
}
bool handleSyntaxMap(unsigned Offset, unsigned Length, UIdent Kind) override {
return false;
}
bool handleSemanticAnnotation(unsigned Offset, unsigned Length,
UIdent Kind, bool isSystem) override {
return false;
}
bool beginDocumentSubStructure(unsigned Offset, unsigned Length,
UIdent Kind, UIdent AccessLevel,
UIdent SetterAccessLevel,
unsigned NameOffset,
unsigned NameLength,
unsigned BodyOffset,
unsigned BodyLength,
StringRef DisplayName,
StringRef TypeName,
StringRef RuntimeName,
StringRef SelectorName,
ArrayRef<StringRef> InheritedTypes,
ArrayRef<UIdent> Attrs) override {
return false;
}
bool endDocumentSubStructure() override { return false; }
bool handleDocumentSubStructureElement(UIdent Kind,
unsigned Offset,
unsigned Length) override {
return false;
}
bool recordAffectedRange(unsigned Offset, unsigned Length) override {
return false;
}
bool recordAffectedLineRange(unsigned Line, unsigned Length) override {
return false;
}
bool recordFormattedText(StringRef Text) override { return false; }
bool setDiagnosticStage(UIdent diagStage) override {
DiagStage = diagStage;
return true;
}
bool handleDiagnostic(const DiagnosticEntryInfo &Info,
UIdent DiagStage) override {
Diags.push_back(Info);
return true;
}
bool handleSourceText(StringRef Text) override { return false; }
};
struct DocUpdateMutexState {
std::mutex Mtx;
std::condition_variable CV;
bool HasUpdate = false;
};
class EditTest : public ::testing::Test {
SourceKit::Context *Ctx;
std::shared_ptr<DocUpdateMutexState> DocUpdState;
public:
EditTest() {
// This is avoiding destroying \p SourceKit::Context because another
// thread may be active trying to use it to post notifications.
// FIXME: Use shared_ptr ownership to avoid such issues.
Ctx = new SourceKit::Context(getRuntimeLibPath(),
SourceKit::createSwiftLangSupport,
/*dispatchOnMain=*/false);
auto localDocUpdState = std::make_shared<DocUpdateMutexState>();
Ctx->getNotificationCenter().addDocumentUpdateNotificationReceiver(
[localDocUpdState](StringRef docName) {
std::unique_lock<std::mutex> lk(localDocUpdState->Mtx);
localDocUpdState->HasUpdate = true;
localDocUpdState->CV.notify_one();
});
DocUpdState = localDocUpdState;
}
LangSupport &getLang() { return Ctx->getSwiftLangSupport(); }
void SetUp() override {
llvm::InitializeAllTargets();
llvm::InitializeAllTargetMCs();
llvm::InitializeAllAsmPrinters();
llvm::InitializeAllAsmParsers();
}
void addNotificationReceiver(DocumentUpdateNotificationReceiver Receiver) {
Ctx->getNotificationCenter().addDocumentUpdateNotificationReceiver(Receiver);
}
bool waitForDocUpdate() {
std::chrono::seconds secondsToWait(10);
std::unique_lock<std::mutex> lk(DocUpdState->Mtx);
auto when = std::chrono::system_clock::now() + secondsToWait;
return !DocUpdState->CV.wait_until(
lk, when, [&]() { return DocUpdState->HasUpdate; });
}
void open(const char *DocName, StringRef Text, ArrayRef<const char *> CArgs,
EditorConsumer &Consumer) {
auto Args = makeArgs(DocName, CArgs);
auto Buf = MemoryBuffer::getMemBufferCopy(Text, DocName);
getLang().editorOpen(DocName, Buf.get(), /*EnableSyntaxMap=*/false, Consumer,
Args);
}
void replaceText(StringRef DocName, unsigned Offset, unsigned Length,
StringRef Text, EditorConsumer &Consumer) {
auto Buf = MemoryBuffer::getMemBufferCopy(Text, DocName);
getLang().editorReplaceText(DocName, Buf.get(), Offset, Length, Consumer);
}
unsigned findOffset(StringRef Val, StringRef Text) {
auto pos = Text.find(Val);
assert(pos != StringRef::npos);
return pos;
}
void reset(DiagConsumer &Consumer) {
Consumer.Diags.clear();
Consumer.DiagStage = UIdent();
std::unique_lock<std::mutex> lk(DocUpdState->Mtx);
DocUpdState->HasUpdate = false;
}
private:
std::vector<const char *> makeArgs(const char *DocName,
ArrayRef<const char *> CArgs) {
std::vector<const char *> Args = CArgs;
Args.push_back(DocName);
return Args;
}
};
} // anonymous namespace
static UIdent SemaDiagStage("source.diagnostic.stage.swift.sema");
static UIdent ParseDiagStage("source.diagnostic.stage.swift.parse");
TEST_F(EditTest, DiagsAfterEdit) {
const char *DocName = "/test.swift";
const char *Contents =
"func foo\n"
"let v = 0\n";
const char *Args[] = { "-parse-as-library" };
DiagConsumer Consumer;
open(DocName, Contents, Args, Consumer);
ASSERT_TRUE(Consumer.Diags.size() == 1);
EXPECT_STREQ("expected '(' in argument list of function declaration", Consumer.Diags[0].Description.c_str());
reset(Consumer);
replaceText(DocName, findOffset("func foo", Contents)+strlen("func foo"), 0, "(){}", Consumer);
EXPECT_TRUE(Consumer.Diags.empty());
bool expired = waitForDocUpdate();
ASSERT_FALSE(expired);
reset(Consumer);
replaceText(DocName, 0, 0, StringRef(), Consumer);
EXPECT_TRUE(Consumer.Diags.empty());
// If typecheck completed for the edit we'll get 'sema stage', otherwise
// we'll get 'parser stage' and there will be another doc-update.
if (Consumer.DiagStage == ParseDiagStage) {
bool expired = waitForDocUpdate();
ASSERT_FALSE(expired);
reset(Consumer);
replaceText(DocName, 0, 0, StringRef(), Consumer);
EXPECT_TRUE(Consumer.Diags.empty());
}
EXPECT_EQ(SemaDiagStage, Consumer.DiagStage);
}