mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
We keep seeing nondeterministic failures of this test due to rdar://88652757. Disable it for now.
453 lines
17 KiB
C++
453 lines
17 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 "NullEditorConsumer.h"
|
|
#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 "swift/Basic/LLVMInitialize.h"
|
|
#include "llvm/Support/MemoryBuffer.h"
|
|
#include "llvm/Support/Path.h"
|
|
#include "llvm/Support/TargetSelect.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
using namespace SourceKit;
|
|
using namespace llvm;
|
|
|
|
static StringRef getRuntimeLibPath() {
|
|
return sys::path::parent_path(SWIFTLIB_DIR);
|
|
}
|
|
|
|
static SmallString<128> getSwiftExecutablePath() {
|
|
SmallString<128> path = sys::path::parent_path(getRuntimeLibPath());
|
|
sys::path::append(path, "bin", "swift-frontend");
|
|
return path;
|
|
}
|
|
|
|
static void *createCancellationToken() {
|
|
static std::atomic<size_t> handle(1000);
|
|
return reinterpret_cast<void *>(
|
|
handle.fetch_add(1, std::memory_order_relaxed));
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct TestCursorInfo {
|
|
// Empty if no error.
|
|
std::string Error;
|
|
std::string InternalDiagnostic;
|
|
std::string Name;
|
|
std::string Typename;
|
|
std::string Filename;
|
|
unsigned Offset;
|
|
unsigned Length;
|
|
};
|
|
|
|
class CursorInfoTest : public ::testing::Test {
|
|
SourceKit::Context &Ctx;
|
|
std::atomic<int> NumTasks;
|
|
NullEditorConsumer Consumer;
|
|
|
|
public:
|
|
SourceKit::Context &getContext() { return Ctx; }
|
|
LangSupport &getLang() { return getContext().getSwiftLangSupport(); }
|
|
|
|
void SetUp() override {
|
|
NumTasks = 0;
|
|
}
|
|
|
|
CursorInfoTest()
|
|
: Ctx(*new SourceKit::Context(getSwiftExecutablePath(),
|
|
getRuntimeLibPath(),
|
|
/*diagnosticDocumentationPath*/ "",
|
|
SourceKit::createSwiftLangSupport,
|
|
[](SourceKit::Context &Ctx){ return nullptr; },
|
|
/*dispatchOnMain=*/false)) {
|
|
INITIALIZE_LLVM();
|
|
// 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.
|
|
}
|
|
|
|
void addNotificationReceiver(DocumentUpdateNotificationReceiver Receiver) {
|
|
Ctx.getNotificationCenter()->addDocumentUpdateNotificationReceiver(Receiver);
|
|
}
|
|
|
|
void open(const char *DocName, StringRef Text,
|
|
std::optional<ArrayRef<const char *>> CArgs = std::nullopt) {
|
|
auto Args = CArgs.has_value() ? makeArgs(DocName, *CArgs)
|
|
: std::vector<const char *>{};
|
|
auto Buf = MemoryBuffer::getMemBufferCopy(Text, DocName);
|
|
getLang().editorOpen(DocName, Buf.get(), Consumer, Args, std::nullopt);
|
|
}
|
|
|
|
void replaceText(StringRef DocName, unsigned Offset, unsigned Length,
|
|
StringRef Text) {
|
|
auto Buf = MemoryBuffer::getMemBufferCopy(Text, DocName);
|
|
getLang().editorReplaceText(DocName, Buf.get(), Offset, Length, Consumer);
|
|
}
|
|
|
|
TestCursorInfo
|
|
getCursor(const char *DocName, unsigned Offset, ArrayRef<const char *> CArgs,
|
|
SourceKitCancellationToken CancellationToken = nullptr,
|
|
bool CancelOnSubsequentRequest = false) {
|
|
auto Args = makeArgs(DocName, CArgs);
|
|
Semaphore sema(0);
|
|
|
|
TestCursorInfo TestInfo;
|
|
getLang().getCursorInfo(
|
|
DocName, DocName, Offset, /*Length=*/0, /*Actionables=*/false,
|
|
/*SymbolGraph=*/false, CancelOnSubsequentRequest, Args,
|
|
/*vfsOptions=*/std::nullopt, CancellationToken,
|
|
[&](const RequestResult<CursorInfoData> &Result) {
|
|
assert(!Result.isCancelled());
|
|
if (Result.isError()) {
|
|
TestInfo.Error = Result.getError().str();
|
|
sema.signal();
|
|
return;
|
|
}
|
|
const CursorInfoData &Info = Result.value();
|
|
TestInfo.InternalDiagnostic = Info.InternalDiagnostic.str();
|
|
if (!Info.Symbols.empty()) {
|
|
const CursorSymbolInfo &MainSymbol = Info.Symbols[0];
|
|
TestInfo.Name = std::string(MainSymbol.Name.str());
|
|
TestInfo.Typename = MainSymbol.TypeName.str();
|
|
TestInfo.Filename = MainSymbol.Location.Filename.str();
|
|
TestInfo.Offset = MainSymbol.Location.Offset;
|
|
TestInfo.Length = MainSymbol.Location.Length;
|
|
}
|
|
sema.signal();
|
|
});
|
|
|
|
bool expired = sema.wait(60 * 1000);
|
|
if (expired)
|
|
llvm::report_fatal_error("check took too long");
|
|
return TestInfo;
|
|
}
|
|
|
|
unsigned findOffset(StringRef Val, StringRef Text) {
|
|
auto pos = Text.find(Val);
|
|
assert(pos != StringRef::npos);
|
|
return pos;
|
|
}
|
|
|
|
void setNeedsSema(bool needsSema) { Consumer.needsSema = needsSema; }
|
|
|
|
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
|
|
|
|
TEST_F(CursorInfoTest, FileNotExist) {
|
|
const char *DocName = "test.swift";
|
|
const char *Contents =
|
|
"let foo = 0\n";
|
|
const char *Args[] = { "<not-existent-file>" };
|
|
|
|
open(DocName, Contents);
|
|
auto FooOffs = findOffset("foo =", Contents);
|
|
auto Info = getCursor(DocName, FooOffs, Args);
|
|
EXPECT_STREQ("foo", Info.Name.c_str());
|
|
EXPECT_STREQ("Int", Info.Typename.c_str());
|
|
}
|
|
|
|
static const char *ExpensiveInit =
|
|
"[0:0,0:0,0:0,0:0,0:0,0:0,0:0]";
|
|
|
|
TEST_F(CursorInfoTest, EditAfter) {
|
|
const char *DocName = "test.swift";
|
|
const char *Contents =
|
|
"let value = foo\n"
|
|
"let foo = 0\n";
|
|
const char *Args[] = { "-parse-as-library" };
|
|
|
|
open(DocName, Contents);
|
|
auto FooRefOffs = findOffset("foo", Contents);
|
|
auto FooOffs = findOffset("foo =", Contents);
|
|
auto Info = getCursor(DocName, FooRefOffs, Args);
|
|
EXPECT_STREQ("foo", Info.Name.c_str());
|
|
EXPECT_STREQ("Int", Info.Typename.c_str());
|
|
EXPECT_STREQ(DocName, Info.Filename.c_str());
|
|
EXPECT_EQ(FooOffs, Info.Offset);
|
|
EXPECT_EQ(strlen("foo"), Info.Length);
|
|
|
|
StringRef TextToReplace = "0";
|
|
replaceText(DocName, findOffset(TextToReplace, Contents), TextToReplace.size(),
|
|
ExpensiveInit);
|
|
// Insert a space in front of 'foo' decl.
|
|
replaceText(DocName, FooOffs, 0, " ");
|
|
++FooOffs;
|
|
|
|
// Should not wait for the new AST, it should give the previous answer.
|
|
Info = getCursor(DocName, FooRefOffs, Args);
|
|
EXPECT_STREQ("foo", Info.Name.c_str());
|
|
EXPECT_STREQ("Int", Info.Typename.c_str());
|
|
EXPECT_STREQ(DocName, Info.Filename.c_str());
|
|
EXPECT_EQ(FooOffs, Info.Offset);
|
|
EXPECT_EQ(strlen("foo"), Info.Length);
|
|
}
|
|
|
|
TEST_F(CursorInfoTest, EditBefore) {
|
|
const char *DocName = "test.swift";
|
|
const char *Contents =
|
|
"let foo = 0\n"
|
|
"let value = foo;\n";
|
|
const char *Args[] = { "-parse-as-library" };
|
|
|
|
open(DocName, Contents);
|
|
auto FooRefOffs = findOffset("foo;", Contents);
|
|
auto FooOffs = findOffset("foo =", Contents);
|
|
auto Info = getCursor(DocName, FooRefOffs, Args);
|
|
EXPECT_STREQ("", Info.Error.c_str());
|
|
EXPECT_STREQ("", Info.InternalDiagnostic.c_str());
|
|
EXPECT_STREQ("foo", Info.Name.c_str());
|
|
EXPECT_STREQ("Int", Info.Typename.c_str());
|
|
EXPECT_STREQ(DocName, Info.Filename.c_str());
|
|
EXPECT_EQ(FooOffs, Info.Offset);
|
|
EXPECT_EQ(strlen("foo"), Info.Length);
|
|
|
|
StringRef TextToReplace = "0";
|
|
replaceText(DocName, findOffset(TextToReplace, Contents), TextToReplace.size(),
|
|
ExpensiveInit);
|
|
FooRefOffs += StringRef(ExpensiveInit).size() - TextToReplace.size();
|
|
// Insert a space in front of 'foo' decl.
|
|
replaceText(DocName, FooOffs, 0, " ");
|
|
++FooOffs;
|
|
++FooRefOffs;
|
|
|
|
// Should not wait for the new AST, it should give the previous answer.
|
|
Info = getCursor(DocName, FooRefOffs, Args);
|
|
EXPECT_STREQ("", Info.Error.c_str());
|
|
EXPECT_STREQ("", Info.InternalDiagnostic.c_str());
|
|
EXPECT_STREQ("foo", Info.Name.c_str());
|
|
EXPECT_STREQ("Int", Info.Typename.c_str());
|
|
EXPECT_STREQ(DocName, Info.Filename.c_str());
|
|
EXPECT_EQ(FooOffs, Info.Offset);
|
|
EXPECT_EQ(strlen("foo"), Info.Length);
|
|
}
|
|
|
|
TEST_F(CursorInfoTest, CursorInfoMustWaitDueDeclLoc) {
|
|
const char *DocName = "test.swift";
|
|
const char *Contents =
|
|
"let value = foo\n"
|
|
"let foo = 0\n";
|
|
const char *Args[] = { "-parse-as-library" };
|
|
|
|
open(DocName, Contents);
|
|
auto FooRefOffs = findOffset("foo", Contents);
|
|
auto FooOffs = findOffset("foo =", Contents);
|
|
auto Info = getCursor(DocName, FooRefOffs, Args);
|
|
EXPECT_STREQ("", Info.Error.c_str());
|
|
EXPECT_STREQ("", Info.InternalDiagnostic.c_str());
|
|
EXPECT_STREQ("foo", Info.Name.c_str());
|
|
EXPECT_STREQ("Int", Info.Typename.c_str());
|
|
|
|
StringRef TextToReplace = "0";
|
|
replaceText(DocName, findOffset(TextToReplace, Contents), TextToReplace.size(),
|
|
ExpensiveInit);
|
|
// Edit over the 'foo' decl.
|
|
replaceText(DocName, FooOffs, strlen("foo"), "foo");
|
|
|
|
// Should wait for the new AST, because the declaration location for the 'foo'
|
|
// reference has been edited out.
|
|
Info = getCursor(DocName, FooRefOffs, Args);
|
|
EXPECT_STREQ("", Info.Error.c_str());
|
|
EXPECT_STREQ("", Info.InternalDiagnostic.c_str());
|
|
EXPECT_STREQ("foo", Info.Name.c_str());
|
|
EXPECT_STREQ("[Int : Int]", Info.Typename.c_str());
|
|
EXPECT_EQ(FooOffs, Info.Offset);
|
|
EXPECT_EQ(strlen("foo"), Info.Length);
|
|
}
|
|
|
|
TEST_F(CursorInfoTest, CursorInfoMustWaitDueOffset) {
|
|
const char *DocName = "test.swift";
|
|
const char *Contents =
|
|
"let value = foo\n"
|
|
"let foo = 0\n";
|
|
const char *Args[] = { "-parse-as-library" };
|
|
|
|
open(DocName, Contents);
|
|
auto FooRefOffs = findOffset("foo", Contents);
|
|
auto FooOffs = findOffset("foo =", Contents);
|
|
auto Info = getCursor(DocName, FooRefOffs, Args);
|
|
EXPECT_STREQ("foo", Info.Name.c_str());
|
|
EXPECT_STREQ("Int", Info.Typename.c_str());
|
|
|
|
StringRef TextToReplace = "0";
|
|
replaceText(DocName, findOffset(TextToReplace, Contents), TextToReplace.size(),
|
|
ExpensiveInit);
|
|
// Edit over the 'foo' reference.
|
|
replaceText(DocName, FooRefOffs, strlen("foo"), "foo");
|
|
|
|
// Should wait for the new AST, because the cursor location has been edited
|
|
// out.
|
|
Info = getCursor(DocName, FooRefOffs, Args);
|
|
EXPECT_STREQ("foo", Info.Name.c_str());
|
|
EXPECT_STREQ("[Int : Int]", Info.Typename.c_str());
|
|
EXPECT_EQ(FooOffs, Info.Offset);
|
|
EXPECT_EQ(strlen("foo"), Info.Length);
|
|
}
|
|
|
|
TEST_F(CursorInfoTest, CursorInfoMustWaitDueToken) {
|
|
const char *DocName = "test.swift";
|
|
const char *Contents =
|
|
"let value = foo\n"
|
|
"let foo = 0\n";
|
|
const char *Args[] = { "-parse-as-library" };
|
|
|
|
open(DocName, Contents);
|
|
auto FooRefOffs = findOffset("foo", Contents);
|
|
auto FooOffs = findOffset("foo =", Contents);
|
|
auto Info = getCursor(DocName, FooRefOffs, Args);
|
|
EXPECT_STREQ("foo", Info.Name.c_str());
|
|
EXPECT_STREQ("Int", Info.Typename.c_str());
|
|
|
|
StringRef TextToReplace = "0";
|
|
replaceText(DocName, findOffset(TextToReplace, Contents), TextToReplace.size(),
|
|
ExpensiveInit);
|
|
// Change 'foo' to 'fog' by replacing the last character.
|
|
replaceText(DocName, FooOffs+2, 1, "g");
|
|
replaceText(DocName, FooRefOffs+2, 1, "g");
|
|
|
|
// Should wait for the new AST, because the cursor location points to a
|
|
// different token.
|
|
Info = getCursor(DocName, FooRefOffs, Args);
|
|
EXPECT_STREQ("fog", Info.Name.c_str());
|
|
EXPECT_STREQ("[Int : Int]", Info.Typename.c_str());
|
|
EXPECT_EQ(FooOffs, Info.Offset);
|
|
EXPECT_EQ(strlen("fog"), Info.Length);
|
|
}
|
|
|
|
TEST_F(CursorInfoTest, DISABLED_CursorInfoMustWaitDueTokenRace) {
|
|
// Disabled due to a race condition (rdar://88652757)
|
|
const char *DocName = "test.swift";
|
|
const char *Contents = "let value = foo\n"
|
|
"let foo = 0\n";
|
|
const char *Args[] = {"-parse-as-library"};
|
|
|
|
auto FooRefOffs = findOffset("foo", Contents);
|
|
auto FooOffs = findOffset("foo =", Contents);
|
|
|
|
// Open with args, kicking off an ast build. The hope of this tests is for
|
|
// this AST to still be in the process of building when we start the cursor
|
|
// info, to ensure the ASTManager doesn't try to handle this cursor info with
|
|
// the wrong AST.
|
|
setNeedsSema(true);
|
|
open(DocName, Contents, llvm::ArrayRef(Args));
|
|
// Change 'foo' to 'fog' by replacing the last character.
|
|
replaceText(DocName, FooOffs + 2, 1, "g");
|
|
replaceText(DocName, FooRefOffs + 2, 1, "g");
|
|
|
|
// Should wait for the new AST, because the cursor location points to a
|
|
// different token.
|
|
auto Info = getCursor(DocName, FooRefOffs, Args);
|
|
EXPECT_STREQ("fog", Info.Name.c_str());
|
|
EXPECT_STREQ("Int", Info.Typename.c_str());
|
|
EXPECT_EQ(FooOffs, Info.Offset);
|
|
EXPECT_EQ(strlen("fog"), Info.Length);
|
|
}
|
|
|
|
TEST_F(CursorInfoTest, CursorInfoCancelsPreviousRequest) {
|
|
// TODO: This test case relies on the following snippet being slow to type
|
|
// check so that the first cursor info request takes longer to execute than it
|
|
// takes time to schedule the second request. If that is fixed, we need to
|
|
// find a new way to cause slow type checking. rdar://80582770
|
|
const char *SlowDocName = "slow.swift";
|
|
const char *SlowContents = "func foo(x: Invalid1, y: Invalid2) {\n"
|
|
" x / y / x / y / x / y / x / y\n"
|
|
"}\n";
|
|
auto SlowOffset = findOffset("x", SlowContents);
|
|
const char *Args[] = {"-parse-as-library"};
|
|
std::vector<const char *> ArgsForSlow = llvm::ArrayRef(Args).vec();
|
|
ArgsForSlow.push_back(SlowDocName);
|
|
|
|
const char *FastDocName = "fast.swift";
|
|
const char *FastContents = "func bar() {\n"
|
|
" let foo = 123\n"
|
|
"}\n";
|
|
auto FastOffset = findOffset("foo", FastContents);
|
|
std::vector<const char *> ArgsForFast = llvm::ArrayRef(Args).vec();
|
|
ArgsForFast.push_back(FastDocName);
|
|
|
|
open(SlowDocName, SlowContents, llvm::ArrayRef(Args));
|
|
open(FastDocName, FastContents, llvm::ArrayRef(Args));
|
|
|
|
// Schedule a cursor info request that takes long to execute. This should be
|
|
// cancelled as the next cursor info (which is faster) gets requested.
|
|
Semaphore FirstCursorInfoSema(0);
|
|
getLang().getCursorInfo(
|
|
SlowDocName, SlowDocName, SlowOffset, /*Length=*/0, /*Actionables=*/false,
|
|
/*SymbolGraph=*/false, /*CancelOnSubsequentRequest=*/true, ArgsForSlow,
|
|
/*vfsOptions=*/std::nullopt, /*CancellationToken=*/nullptr,
|
|
[&](const RequestResult<CursorInfoData> &Result) {
|
|
EXPECT_TRUE(Result.isCancelled());
|
|
FirstCursorInfoSema.signal();
|
|
});
|
|
|
|
auto Info = getCursor(FastDocName, FastOffset, Args,
|
|
/*CancellationToken=*/nullptr,
|
|
/*CancelOnSubsequentRequest=*/true);
|
|
EXPECT_STREQ("foo", Info.Name.c_str());
|
|
EXPECT_STREQ("Int", Info.Typename.c_str());
|
|
EXPECT_EQ(FastOffset, Info.Offset);
|
|
EXPECT_EQ(strlen("foo"), Info.Length);
|
|
|
|
bool expired = FirstCursorInfoSema.wait(30 * 1000);
|
|
if (expired)
|
|
llvm::report_fatal_error("Did not receive a response for the first request");
|
|
}
|
|
|
|
TEST_F(CursorInfoTest, CursorInfoCancellation) {
|
|
// TODO: This test case relies on the following snippet being slow to type
|
|
// check so that the first cursor info request takes longer to execute than it
|
|
// takes time to schedule the second request. If that is fixed, we need to
|
|
// find a new way to cause slow type checking. rdar://80582770
|
|
const char *SlowDocName = "slow.swift";
|
|
const char *SlowContents = "func foo(x: Invalid1, y: Invalid2) {\n"
|
|
" x / y / x / y / x / y / x / y\n"
|
|
"}\n";
|
|
auto SlowOffset = findOffset("x", SlowContents);
|
|
const char *Args[] = {"-parse-as-library"};
|
|
std::vector<const char *> ArgsForSlow = llvm::ArrayRef(Args).vec();
|
|
ArgsForSlow.push_back(SlowDocName);
|
|
|
|
open(SlowDocName, SlowContents, llvm::ArrayRef(Args));
|
|
|
|
SourceKitCancellationToken CancellationToken = createCancellationToken();
|
|
|
|
// Schedule a cursor info request that takes long to execute. This should be
|
|
// cancelled as the next cursor info (which is faster) gets requested.
|
|
Semaphore CursorInfoSema(0);
|
|
getLang().getCursorInfo(
|
|
SlowDocName, SlowDocName, SlowOffset, /*Length=*/0, /*Actionables=*/false,
|
|
/*SymbolGraph=*/false, /*CancelOnSubsequentRequest=*/false, ArgsForSlow,
|
|
/*vfsOptions=*/std::nullopt, /*CancellationToken=*/CancellationToken,
|
|
[&](const RequestResult<CursorInfoData> &Result) {
|
|
EXPECT_TRUE(Result.isCancelled());
|
|
CursorInfoSema.signal();
|
|
});
|
|
|
|
getContext().getRequestTracker()->cancel(CancellationToken);
|
|
|
|
bool expired = CursorInfoSema.wait(30 * 1000);
|
|
if (expired)
|
|
llvm::report_fatal_error("Did not receive a response for the first request");
|
|
}
|