diff --git a/src/FileManager.cpp b/src/FileManager.cpp index 3f55112d4..b99908b20 100644 --- a/src/FileManager.cpp +++ b/src/FileManager.cpp @@ -35,6 +35,9 @@ void FileManager::init(const std::shared_ptr &proj, Mode mode) void FileManager::reload(Mode mode) { + if (!Server::instance()->options().tests.isEmpty()) + mode = Synchronous; + mLastReloadTime = Rct::monoMs(); std::shared_ptr project = mProject.lock(); assert(project); diff --git a/src/FollowLocationJob.cpp b/src/FollowLocationJob.cpp index f486a2fb3..d22d5d820 100644 --- a/src/FollowLocationJob.cpp +++ b/src/FollowLocationJob.cpp @@ -29,8 +29,9 @@ int FollowLocationJob::execute() const SymbolMap &map = project()->symbols(); SymbolMap::const_iterator it = RTags::findCursorInfo(map, location); - if (it == map.end()) + if (it == map.end()) { return 1; + } const std::shared_ptr &cursorInfo = it->second; if (cursorInfo && cursorInfo->isClass() && cursorInfo->isDefinition()) { diff --git a/src/Location.h b/src/Location.h index b51d04589..5247a4af0 100644 --- a/src/Location.h +++ b/src/Location.h @@ -162,14 +162,14 @@ class Location error("Failed to make location from [%s:%d:%d]", path.constData(), line, col); return Location(); } - static String encode(const String &key) + static String encode(const String &key, const Path &pwd = Path()) { char path[PATH_MAX]; uint32_t line, col; if (sscanf(key.constData(), "%[^':']:%d:%d", path, &line, &col) != 3) return String(); - Path resolved = Path::resolved(path, Path::MakeAbsolute); + Path resolved = Path::resolved(path, Path::MakeAbsolute, pwd); { char buf[8]; memcpy(buf, &line, sizeof(line)); @@ -180,14 +180,14 @@ class Location return resolved; } - static Location fromPathLineAndColumn(const String &str) + static Location fromPathLineAndColumn(const String &str, const Path &pwd = Path()) { char path[PATH_MAX]; uint32_t line, col; if (sscanf(str.constData(), "%[^':']:%d:%d", path, &line, &col) != 3) return Location(); - const Path resolved = Path::resolved(path); + const Path resolved = Path::resolved(path, Path::RealPath, pwd); return Location(Location::insertFile(resolved), line, col); } static Hash idsToPaths() diff --git a/src/Project.cpp b/src/Project.cpp index 584396ede..cf7521ea8 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -529,10 +529,10 @@ void Project::onJobFinished(const std::shared_ptr &job, const std::s Location::path(fileId).toTilde().constData()); } - if (mActiveJobs.isEmpty()) { - mSyncTimer.restart(indexData->flags & IndexerJob::Dirty ? 0 : SyncTimeout, Timer::SingleShot); - } else if (options.syncThreshold && mIndexData.size() >= options.syncThreshold) { + if (options.syncThreshold && mIndexData.size() >= options.syncThreshold) { startSync(Sync_Asynchronous); + } else if (mActiveJobs.isEmpty()) { + mSyncTimer.restart(indexData->flags & IndexerJob::Dirty ? 0 : SyncTimeout, Timer::SingleShot); } } @@ -1015,6 +1015,8 @@ String Project::fixIts(uint32_t fileId) const bool Project::startSync(SyncMode mode) { + if (!Server::instance()->options().tests.isEmpty()) + mode = Sync_Synchronous; if (mState != Loaded) { if (mode == Sync_Asynchronous) mSyncTimer.restart(SyncTimeout, Timer::SingleShot); diff --git a/src/QueryMessage.cpp b/src/QueryMessage.cpp index 3ca8fa81f..492322890 100644 --- a/src/QueryMessage.cpp +++ b/src/QueryMessage.cpp @@ -52,3 +52,67 @@ Match QueryMessage::match() const return Match(mQuery, flags); } + +QueryMessage::Flag QueryMessage::flagFromString(const String &string) +{ + if (string == "no-context") { + return NoContext; + } else if (string == "filter-system-includes") { + return FilterSystemIncludes; + } else if (string == "strip-parentheses") { + return StripParentheses; + } else if (string == "all-references") { + return AllReferences; + } else if (string == "reverse-sort") { + return ReverseSort; + } else if (string == "elisp-list") { + return ElispList; + } else if (string == "imenu") { + return IMenu; + } else if (string == "match-regexp") { + return MatchRegexp; + } else if (string == "match-case-insensitive") { + return MatchCaseInsensitive; + } else if (string == "find-virtuals") { + return FindVirtuals; + } else if (string == "silent") { + return Silent; + } else if (string == "absolute-path") { + return AbsolutePath; + } else if (string == "find-file-prefer-exact") { + return FindFilePreferExact; + } else if (string == "cursor-info-include-parents") { + return CursorInfoIncludeParents; + } else if (string == "cursor-info-include-targets") { + return CursorInfoIncludeTargets; + } else if (string == "cursor-info-include-references") { + return CursorInfoIncludeReferences; + } else if (string == "declaration-only") { + return DeclarationOnly; + } else if (string == "containing-function") { + return ContainingFunction; + } else if (string == "wait-for-load-project") { + return WaitForLoadProject; + } else if (string == "cursor-kind") { + return CursorKind; + } else if (string == "display-name") { + return DisplayName; + } else if (string == "compilation-flags-only") { + return CompilationFlagsOnly; + } else if (string == "compilation-flags-split-line") { + return CompilationFlagsSplitLine; + } else if (string == "dump-include-headers") { + return DumpIncludeHeaders; + } else if (string == "silent-query") { + return SilentQuery; + } else if (string == "synchronous-completions") { + return SynchronousCompletions; + } else if (string == "no-sort-references-by-input") { + return NoSortReferencesByInput; + } else if (string == "has-location") { + return HasLocation; + } else if (string == "wildcard-symbol-names") { + return WildcardSymbolNames; + } + return NoFlag; +} diff --git a/src/QueryMessage.h b/src/QueryMessage.h index adf6b8d38..aa87af7c5 100644 --- a/src/QueryMessage.h +++ b/src/QueryMessage.h @@ -66,6 +66,7 @@ class QueryMessage : public RTagsMessage }; enum Flag { + NoFlag = 0x00000000, NoContext = 0x00000001, FilterSystemIncludes = 0x00000004, StripParentheses = 0x00000008, @@ -150,6 +151,16 @@ class QueryMessage : public RTagsMessage } } + void setFlag(Flag flag, bool on = true) + { + if (on) { + mFlags |= flag; + } else { + mFlags &= ~flag; + } + } + + static Flag flagFromString(const String &string); static unsigned keyFlags(unsigned queryFlags); inline unsigned keyFlags() const { return keyFlags(mFlags); } diff --git a/src/Server.cpp b/src/Server.cpp index eb86d95ed..5a50b89c4 100644 --- a/src/Server.cpp +++ b/src/Server.cpp @@ -46,6 +46,7 @@ #include "StatusJob.h" #include #include +#include #include #include #include @@ -191,6 +192,7 @@ bool Server::init(const Options &options) for (int i=0; i<10; ++i) { mUnixServer.reset(new SocketServer); + warning() << "listening" << mOptions.socketFile; if (mUnixServer->listen(mOptions.socketFile)) { break; } @@ -404,8 +406,10 @@ void Server::handleIndexMessage(const std::shared_ptr &message, Co CXCompilationDatabase_Error err; CXCompilationDatabase db = clang_CompilationDatabase_fromDirectory(path.constData(), &err); if (err != CXCompilationDatabase_NoError) { - conn->write("Can't load compilation database"); - conn->finish(); + if (conn) { + conn->write("Can't load compilation database"); + conn->finish(); + } return; } CXCompileCommands cmds = clang_CompilationDatabase_getAllCompileCommands(db); @@ -429,14 +433,17 @@ void Server::handleIndexMessage(const std::shared_ptr &message, Co } clang_CompileCommands_dispose(cmds); clang_CompilationDatabase_dispose(db); - conn->write("[Server] Compilation database loaded"); - conn->finish(); + if (conn) { + conn->write("[Server] Compilation database loaded"); + conn->finish(); + } return; } #endif const bool ret = index(message->arguments(), message->workingDirectory(), message->projectRoot(), message->escape()); - conn->finish(ret ? 0 : 1); + if (conn) + conn->finish(ret ? 0 : 1); } void Server::handleLogOutputMessage(const std::shared_ptr &message, Connection *conn) @@ -448,6 +455,7 @@ void Server::handleIndexerMessage(const std::shared_ptr &message { mJobScheduler->handleIndexerMessage(message); conn->finish(); + mIndexerMessageReceived(); } void Server::handleQueryMessage(const std::shared_ptr &message, Connection *conn) @@ -1469,3 +1477,196 @@ void Server::dumpJobs(Connection *conn) { mJobScheduler->dump(conn); } + +class TestConnection : public Connection +{ +public: + TestConnection(const Path &workingDirectory) + : Connection(), mIsFinished(false), mWorkingDirectory(workingDirectory) + {} + virtual bool send(const Message &message) + { + if (message.messageId() == Message::FinishMessageId) { + mIsFinished = true; + finished()(this, 0); + } else if (message.messageId() == Message::ResponseId) { + String response = reinterpret_cast(message).data(); + if (response.startsWith(mWorkingDirectory)) { + response.remove(0, mWorkingDirectory.size()); + } + mOutput.append(response); + } + return true; + } + List output() const { return mOutput; } + bool isFinished() const { return mIsFinished; } +private: + bool mIsFinished; + List mOutput; + const Path mWorkingDirectory; +}; + +bool Server::runTests() +{ + assert(!mOptions.tests.isEmpty()); + bool ret = true; + int sourceCount = 0; + mIndexerMessageReceived.connect([&sourceCount]() { + // error() << "Got a finish" << sourceCount; + assert(sourceCount > 0); + if (!--sourceCount) { + EventLoop::eventLoop()->quit(); + } + }); + for (const auto &file : mOptions.tests) { + const String fileContents = file.readAll(); + if (fileContents.isEmpty()) { + error() << "Failed to open file" << file; + ret = false; + continue; + } + bool ok = true; + const Value value = Value::fromJSON(fileContents, &ok); + if (!ok || !value.isMap()) { + error() << "Failed to parse json" << file << ok << value.type() << value; + ret = false; + continue; + } + const Map tests = value.operator[] >("tests"); + if (tests.isEmpty()) { + error() << "Invalid test" << file; + ret = false; + continue; + } + const List sources = value.operator[] >("sources"); + if (sources.isEmpty()) { + error() << "Invalid test" << file; + ret = false; + continue; + } + const Path workingDirectory = file.parentDir(); + const Path projectRoot = RTags::findProjectRoot(workingDirectory, RTags::SourceRoot); + if (projectRoot.isEmpty()) { + error() << "Can't find project root" << workingDirectory; + ret = false; + continue; + } + for (const auto &source : sources) { + if (!source.isString()) { + error() << "Invalid source" << source; + ret = false; + continue; + } + if (!index("clang " + source.convert(), workingDirectory, workingDirectory, false)) { + error() << "Failed to index" << ("clang " + source.convert()) << workingDirectory; + ret = false; + continue; + } + ++sourceCount; + } + mOptions.syncThreshold = sourceCount; + EventLoop::eventLoop()->exec(mOptions.testTimeout); + if (sourceCount) { + error() << "Timed out waiting for sources to compile"; + sourceCount = 0; + ret = false; + continue; + } + + for (const auto &test : tests) { + if (!test.second.isMap()) { + error() << "Invalid test" << test.second.type(); + ret = false; + continue; + } + const String type = test.second.operator[]("type"); + if (type.isEmpty()) { + error() << "Invalid test. No type"; + ret = false; + continue; + } + std::shared_ptr query; + if (type == "follow-location") { + const String location = Location::encode(test.second.operator[]("location"), workingDirectory); + if (location.isEmpty()) { + error() << "Invalid test. Invalid location"; + ret = false; + continue; + } + query.reset(new QueryMessage(QueryMessage::FollowLocation)); + query->setQuery(location); + } else if (type == "references") { + const String location = Location::encode(test.second.operator[]("location"), workingDirectory); + if (location.isEmpty()) { + error() << "Invalid test. Invalid location"; + ret = false; + continue; + } + query.reset(new QueryMessage(QueryMessage::ReferencesLocation)); + query->setQuery(location); + } else if (type == "references-name") { + const String name = test.second.operator[]("name"); + if (name.isEmpty()) { + error() << "Invalid test. Invalid name"; + ret = false; + continue; + } + query.reset(new QueryMessage(QueryMessage::ReferencesLocation)); + query->setQuery(name); + } else { + error() << "Unknown test" << type; + ret = false; + continue; + } + const List flags = test.second.operator[] >("flags"); + for (const auto &flag : flags) { + if (!flag.isString()) { + error() << "Invalid flag"; + ret = false; + } else { + const QueryMessage::Flag f = QueryMessage::flagFromString(flag.convert()); + if (f == QueryMessage::NoFlag) { + error() << "Invalid flag"; + ret = false; + continue; + } + query->setFlag(f); + } + } + TestConnection conn(workingDirectory); + query->setFlag(QueryMessage::SilentQuery); + handleQueryMessage(query, &conn); + if (!conn.isFinished()) { + error() << "Query failed"; + ret = false; + continue; + } + + const Value out = test.second["output"]; + if (!out.isList()) { + error() << "Invalid output"; + ret = false; + continue; + } + List output; + for (auto it=out.listBegin(); it != out.listEnd(); ++it) { + if (!it->isString()) { + error() << "Invalid output"; + ret = false; + continue; + } + output.append(it->convert()); + } + if (output != conn.output()) { + error() << "Test failed. Expected:"; + error() << output; + error() << "Got:"; + error() << conn.output(); + ret = false; + } else { + error() << "Test passed"; + } + } + } + return ret; +} diff --git a/src/Server.h b/src/Server.h index 3a634bc09..5dbbf97b6 100644 --- a/src/Server.h +++ b/src/Server.h @@ -76,23 +76,26 @@ class Server : options(0), jobCount(0), unloadTimer(0), rpVisitFileTimeout(0), rpIndexerMessageTimeout(0), rpConnectTimeout(0), rpNiceValue(0), syncThreshold(0), threadStackSize(0), maxCrashCount(0), - completionCacheSize(0), astCache(0) + completionCacheSize(0), astCache(0), testTimeout(60 * 1000 * 5) {} Path socketFile, dataDir; unsigned options; int jobCount, unloadTimer, rpVisitFileTimeout, rpIndexerMessageTimeout, rpConnectTimeout, rpNiceValue, - syncThreshold, threadStackSize, maxCrashCount, completionCacheSize, astCache; + syncThreshold, threadStackSize, maxCrashCount, completionCacheSize, astCache, + testTimeout; List defaultArguments, excludeFilters; Set blockedArguments; List includePaths; List defines; + List tests; Set ignoredCompilers; List > extraCompilers; inline bool flag(enum Option o) const { return 0 != (options & o); } }; bool init(const Options &options); + bool runTests(); const Options &options() const { return mOptions; } bool suspended() const { return mSuspended; } bool saveFileIds(); @@ -177,6 +180,8 @@ class Server std::shared_ptr mJobScheduler; CompletionThread *mCompletionThread; + + Signal > mIndexerMessageReceived; }; #endif diff --git a/src/rdm.cpp b/src/rdm.cpp index 8930cf490..0728db5df 100644 --- a/src/rdm.cpp +++ b/src/rdm.cpp @@ -63,6 +63,8 @@ static void usage(FILE *f) "\nServer options:\n" " --clear-project-caches|-C Clear out project caches.\n" + " --test|-J [arg] Run this test.\n" + " --test-timeout|-z [arg] Timeout for test to complete.\n" " --completion-cache-size|-i [arg] Number of translation units to cache (default " STR(DEFAULT_COMPLETION_CACHE_SIZE) ").\n" " --config|-c [arg] Use this file instead of ~/.rdmrc.\n" " --data-dir|-d [arg] Use this directory to store persistent data (default ~/.rtags).\n" @@ -89,7 +91,7 @@ static void usage(FILE *f) " --rp-connect-timeout|-O [arg] Timeout for connection from rp to rdm in ms (0 means no timeout) (default " STR(DEFAULT_RP_CONNECT_TIMEOUT) ").\n" " --rp-indexer-message-timeout|-T [arg] Timeout for rp indexer-message in ms (0 means no timeout) (default " STR(DEFAULT_RP_INDEXER_MESSAGE_TIMEOUT) ").\n" " --rp-nice-value|-a [arg] Nice value to use for rp (nice(2)) (default -1, e.g. not nicing).\n" - " --rp-visit-file-timeout|-t [arg] Timeout for rp visitfile commands in ms (0 means no timeout) (default " STR(DEFAULT_RP_VISITFILE_TIMEOUT) ").\n" + " --rp-visit-file-timeout|-J [arg] Timeout for rp visitfile commands in ms (0 means no timeout) (default " STR(DEFAULT_RP_VISITFILE_TIMEOUT) ").\n" " --separate-debug-and-release|-E Normally rdm doesn't consider release and debug as different builds. Pass this if you want it to.\n" " --setenv|-e [arg] Set this environment variable (--setenv \"foobar=1\").\n" " --silent|-S No logging to stdout.\n" @@ -143,6 +145,8 @@ int main(int argc, char** argv) { "cache-AST", required_argument, 0, 'A' }, { "verbose", no_argument, 0, 'v' }, { "job-count", required_argument, 0, 'j' }, + { "test", required_argument, 0, 't' }, + { "test-timeout", required_argument, 0, 'z' }, { "clean-slate", no_argument, 0, 'C' }, { "disable-sighandler", no_argument, 0, 'x' }, { "silent", no_argument, 0, 'S' }, @@ -163,7 +167,7 @@ int main(int argc, char** argv) { "no-no-unknown-warnings-option", no_argument, 0, 'Y' }, { "ignore-compiler", required_argument, 0, 'b' }, { "watch-system-paths", no_argument, 0, 'w' }, - { "rp-visit-file-timeout", required_argument, 0, 't' }, + { "rp-visit-file-timeout", required_argument, 0, 'J' }, { "rp-indexer-message-timeout", required_argument, 0, 'T' }, { "rp-connect-timeout", required_argument, 0, 'O' }, { "rp-nice-value", required_argument, 0, 'a' }, @@ -364,10 +368,10 @@ int main(int argc, char** argv) case 'Q': serverOpts.options |= Server::StartSuspended; break; - case 't': + case 'J': serverOpts.rpVisitFileTimeout = atoi(optarg); if (serverOpts.rpVisitFileTimeout < 0) { - fprintf(stderr, "Invalid argument to -t %s\n", optarg); + fprintf(stderr, "Invalid argument to -J %s\n", optarg); return 1; } if (!serverOpts.rpVisitFileTimeout) @@ -390,6 +394,21 @@ int main(int argc, char** argv) case 'b': serverOpts.ignoredCompilers.insert(Path::resolved(optarg)); break; + case 't': { + Path test(optarg); + if (!test.resolve() || !test.isFile()) { + fprintf(stderr, "%s doesn't seem to be a file\n", optarg); + return 1; + } + serverOpts.tests += test; + break; } + case 'z': + serverOpts.testTimeout = atoi(optarg); + if (serverOpts.testTimeout <= 0) { + fprintf(stderr, "Invalid argument to -z %s\n", optarg); + return 1; + } + break; case 'n': serverOpts.socketFile = optarg; break; @@ -553,7 +572,7 @@ int main(int argc, char** argv) ++logLevel; break; case '?': { - fprintf(stderr, "Run rc --help for help\n"); + fprintf(stderr, "Run rdm --help for help\n"); return 1; } } } @@ -578,12 +597,38 @@ int main(int argc, char** argv) loop->init(EventLoop::MainEventLoop|EventLoop::EnableSigIntHandler); std::shared_ptr server(new Server); + if (!serverOpts.tests.isEmpty()) { + char buf[1024]; + Path path; + while (true) { + strcpy(buf, "/tmp/rtags-test-XXXXXX"); + if (!mkdtemp(buf)) { + fprintf(stderr, "Failed to mkdtemp (%d)\n", errno); + return 1; + } + path = buf; + path.resolve(); + break; + } + serverOpts.dataDir = path; + strcpy(buf, "/tmp/rtags-sock-XXXXXX"); + if (!mkstemp(buf)) { + fprintf(stderr, "Failed to mkstemp (%d)\n", errno); + return 1; + } + serverOpts.socketFile = buf; + serverOpts.socketFile.resolve(); + } serverOpts.dataDir = serverOpts.dataDir.ensureTrailingSlash(); if (!server->init(serverOpts)) { cleanupLogging(); return 1; } + if (!serverOpts.tests.isEmpty()) { + return server->runTests() ? 0 : 1; + } + loop->exec(); cleanupLogging(); return 0; diff --git a/tests/simple/main.cpp b/tests/simple/main.cpp new file mode 100644 index 000000000..91496f58d --- /dev/null +++ b/tests/simple/main.cpp @@ -0,0 +1,9 @@ +void foo() +{ + +} + +int main() +{ + foo(); +} diff --git a/tests/simple/test.json b/tests/simple/test.json new file mode 100644 index 000000000..3515355f0 --- /dev/null +++ b/tests/simple/test.json @@ -0,0 +1,11 @@ +{ + "sources": [ "-I. main.cpp" ], + "tests": { + "simple": { + "type": "follow-location", + "flags": [ "no-context" ], + "location": "main.cpp:8:6:", + "output": [ "main.cpp:1:6:" ] + } + } +} diff --git a/tests/test.json b/tests/test.json deleted file mode 100644 index 92a27dcad..000000000 --- a/tests/test.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - sources: [ "-I. main.cpp" ], - tests: { - "simple": { - "command": { "follow-symbol", "main.cpp,20" ], - "output": [ "main.cpp,10" ] - } - } -}