blob: 0ba2cb89f3ebd9b097b303bf70f191f57e1bc722 [file] [log] [blame]
//===----------------------------------------------------------------------===//
//
// 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,
unsigned DocOffset,
unsigned DocLength,
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_EQ(1u, Consumer.Diags.size());
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);
}