blob: 2294f17585ac2dbc633d1153134f0881bfc0028b [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 syntaxMapEnabled() override { return true; }
void handleSyntaxMap(unsigned Offset, unsigned Length, UIdent Kind) override {
}
void handleSemanticAnnotation(unsigned Offset, unsigned Length, UIdent Kind,
bool isSystem) override {}
bool documentStructureEnabled() override { return false; }
void 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<std::tuple<UIdent, unsigned, unsigned>> Attrs) override {
}
void endDocumentSubStructure() override {}
void handleDocumentSubStructureElement(UIdent Kind, unsigned Offset,
unsigned Length) override {}
void recordAffectedRange(unsigned Offset, unsigned Length) override {}
void recordAffectedLineRange(unsigned Line, unsigned Length) override {}
void recordFormattedText(StringRef Text) override {}
void setDiagnosticStage(UIdent diagStage) override { DiagStage = diagStage; }
void handleDiagnostic(const DiagnosticEntryInfo &Info,
UIdent DiagStage) override {
Diags.push_back(Info);
}
void handleSourceText(StringRef Text) override {}
void handleSyntaxTree(const swift::syntax::SourceFileSyntax &SyntaxTree,
std::unordered_set<unsigned> &ReusedNodeIds) override {}
SyntaxTreeTransferMode syntaxTreeTransferMode() override {
return SyntaxTreeTransferMode::Off;
}
};
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(bool reset = false) {
std::chrono::seconds secondsToWait(20);
std::unique_lock<std::mutex> lk(DocUpdState->Mtx);
auto when = std::chrono::system_clock::now() + secondsToWait;
auto result = !DocUpdState->CV.wait_until(
lk, when, [&]() { return DocUpdState->HasUpdate; });
if (reset)
DocUpdState->HasUpdate = false;
return result;
}
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(), Consumer, Args);
}
void close(const char *DocName) {
getLang().editorClose(DocName, /*removeCache=*/false);
}
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;
}
void doubleOpenWithDelay(useconds_t delay, bool close);
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);
}
void EditTest::doubleOpenWithDelay(useconds_t delay, bool closeDoc) {
const char *DocName = "/test.swift";
const char *Contents =
"func foo() { _ = unknown_name }\n";
const char *Args[] = { "-parse-as-library" };
DiagConsumer Consumer;
open(DocName, Contents, Args, Consumer);
ASSERT_EQ(0u, Consumer.Diags.size());
// Open again without closing; this reinitializes the semantic info on the doc
if (delay)
usleep(delay);
if (closeDoc)
close(DocName);
reset(Consumer);
open(DocName, Contents, Args, Consumer);
ASSERT_EQ(0u, Consumer.Diags.size());
// Wait for the document update from the second time we open the document. We
// may or may not get a notification from the first time it was opened, but
// only the second time will there be any semantic information available to
// be queried, since the semantic info from the first open is unreachable.
for (int i = 0; i < 2; ++i) {
bool expired = waitForDocUpdate(/*reset=*/true);
ASSERT_FALSE(expired) << "no " << (i ? "second " : "") << "notification";
replaceText(DocName, 0, 0, StringRef(), Consumer);
if (!Consumer.Diags.empty())
break;
ASSERT_EQ(0, i) << "no diagnostics after second notification";
}
ASSERT_EQ(1u, Consumer.Diags.size());
EXPECT_STREQ("use of unresolved identifier 'unknown_name'", Consumer.Diags[0].Description.c_str());
close(DocName);
}
// This test is failing occassionally in CI: rdar://45644449
TEST_F(EditTest, DISABLED_DiagsAfterCloseAndReopen) {
// Attempt to open the same file twice in a row. This tests (subject to
// timing) cases where:
// * the 2nd open happens before the first AST starts building
// * the 2nd open happens after the first AST starts building
// * the 2nd open happens after the AST finishes
// The middle case in particular verifies the ASTManager is only calling the
// correct ASTConsumers.
doubleOpenWithDelay(0, true);
doubleOpenWithDelay(1000, true); // 1 ms
doubleOpenWithDelay(10000, true); // 10 ms
doubleOpenWithDelay(100000, true); // 100 ms
}
TEST_F(EditTest, DiagsAfterReopen) {
// See description of DiagsAfterCloseAndReopen, but in this case we don't
// close the original document, causing it to reinitialize instead of create
// a fresh document.
doubleOpenWithDelay(0, false);
doubleOpenWithDelay(1000, false); // 1 ms
doubleOpenWithDelay(10000, false); // 10 ms
doubleOpenWithDelay(100000, false); // 100 ms
}