diff --git a/C/tests/CMakeLists.txt b/C/tests/CMakeLists.txt index 96401e86bc..e6953fad97 100644 --- a/C/tests/CMakeLists.txt +++ b/C/tests/CMakeLists.txt @@ -36,6 +36,8 @@ elseif(APPLE) include("${CMAKE_CURRENT_LIST_DIR}/cmake/platform_apple.cmake") elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") include("${CMAKE_CURRENT_LIST_DIR}/cmake/platform_linux.cmake") +elseif(EMSCRIPTEN) + include("${CMAKE_CURRENT_LIST_DIR}/cmake/platform_emscripten.cmake") else() message(FATAL_ERROR "Unsupported platform ${CMAKE_SYSTEM_NAME}") endif() @@ -56,18 +58,27 @@ add_executable( # EE tests: c4DatabaseEncryptionTest.cc c4CertificateTest.cc - + ${TOP}LiteCore/tests/main.cpp - ${TOP}Crypto/SecureRandomize.cc - ${TOP}LiteCore/Support/FilePath.cc - ${TOP}LiteCore/Support/LogDecoder.cc - ${TOP}LiteCore/Support/Logging_Stub.cc - ${TOP}LiteCore/Support/StringUtil.cc ${TOP}LiteCore/Support/TestsCommon.cc - ${TOP}LiteCore/Support/Error.cc ${TOP}vendor/fleece/ObjC/slice+CoreFoundation.cc ) +if (NOT EMSCRIPTEN) + # These files contain internal functionality that isn't exported from the LiteCore shared lib. + # But C4Tests use these, so we have to compile them into its binary. + # This doesn't apply to Emscripten, bc WASM doesn't have shared libs; all linking is static. + target_sources( + C4Tests PRIVATE + ${TOP}Crypto/SecureRandomize.cc + ${TOP}LiteCore/Support/Error.cc + ${TOP}LiteCore/Support/FilePath.cc + ${TOP}LiteCore/Support/LogDecoder.cc + ${TOP}LiteCore/Support/Logging_Stub.cc + ${TOP}LiteCore/Support/StringUtil.cc + ) +endif() + get_directory_property(this_targets DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} BUILDSYSTEM_TARGETS) set(LITECORE_TARGETS ${LITECORE_TARGETS} ${this_targets} PARENT_SCOPE) setup_build() @@ -112,6 +123,7 @@ target_include_directories( ${TOP}vendor/fleece/API ${TOP}vendor/fleece/Fleece/Support ${TOP}C + ${TOP}C/include ${TOP}Crypto ${TOP}Replicator ${TOP}Replicator/tests diff --git a/C/tests/c4DatabaseTest.cc b/C/tests/c4DatabaseTest.cc index 350e7b04f9..f789df853a 100644 --- a/C/tests/c4DatabaseTest.cc +++ b/C/tests/c4DatabaseTest.cc @@ -80,7 +80,11 @@ N_WAY_TEST_CASE_METHOD(C4DatabaseTest, "Database ErrorMessages", "[Database][Err assertMessage(SQLiteDomain, SQLITE_IOERR_ACCESS, "SQLite error 3338", "disk I/O error (3338)"); assertMessage(SQLiteDomain, SQLITE_IOERR, "SQLite error 10", "disk I/O error"); assertMessage(LiteCoreDomain, 15, "LiteCore CorruptData", "data is corrupted"); +#ifdef __EMSCRIPTEN__ + assertMessage(POSIXDomain, ENOENT, "POSIX error 44", "No such file or directory"); +#else assertMessage(POSIXDomain, ENOENT, "POSIX error 2", "No such file or directory"); +#endif assertMessage(LiteCoreDomain, kC4ErrorTransactionNotClosed, "LiteCore TransactionNotClosed", "transaction not closed"); assertMessage(SQLiteDomain, -1234, "SQLite error -1234", "unknown error (-1234)"); diff --git a/C/tests/c4PerfTest.cc b/C/tests/c4PerfTest.cc index 05124436cf..d1d5194757 100644 --- a/C/tests/c4PerfTest.cc +++ b/C/tests/c4PerfTest.cc @@ -175,6 +175,8 @@ class PerfTest : public C4Test { input += "_helium_macos"; #elif defined(__linux__) input += "_helium_linux"; +#elif defined(__EMSCRIPTEN__) + input += "_helium_emscripten"; #else # error "Unknown platform" #endif diff --git a/C/tests/cmake/platform_emscripten.cmake b/C/tests/cmake/platform_emscripten.cmake new file mode 100644 index 0000000000..71048adab0 --- /dev/null +++ b/C/tests/cmake/platform_emscripten.cmake @@ -0,0 +1,31 @@ +function(setup_build) + target_sources( + C4Tests PRIVATE + ${TOP}LiteCore/Unix/strlcat.c + ) + + target_link_libraries( + C4Tests PRIVATE + ) + + target_compile_options( + C4Tests PRIVATE + ${EMSCRIPTEN_COMPILE_FLAGS} + ) + + target_link_options( + C4Tests PRIVATE + ${EMSCRIPTEN_LINK_FLAGS} + "SHELL:-s EXIT_RUNTIME=1" + "SHELL:-s PTHREAD_POOL_SIZE=24" + "SHELL:-s ALLOW_MEMORY_GROWTH=1" + "SHELL:-s WASM_BIGINT=1" + "-lnodefs.js" + "-lnoderawfs.js" + ) + + target_include_directories( + C4Tests PRIVATE + ${TOP}LiteCore/Unix + ) +endfunction() diff --git a/CMakeLists.txt b/CMakeLists.txt index e2ad786878..8427145931 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,7 +127,14 @@ if(BUILD_ENTERPRISE) ) endif() -if(MSVC) +if(EMSCRIPTEN) + # Emscripten does not actually support shared libraries and instead + # just builds a static library for compatibility with existing + # build setups. We just set this property to suppress a CMake warning. + set_property(GLOBAL PROPERTY TARGET_SUPPORTS_SHARED_LIBS TRUE) + + include("${PROJECT_SOURCE_DIR}/cmake/platform_emscripten.cmake") +elseif(MSVC) add_definitions(-DWIN32_LEAN_AND_MEAN -D_WIN32_WINNT=0x0A00) if(WINDOWS_STORE) message(FATAL_ERROR "UWP no longer supported") @@ -143,7 +150,7 @@ elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") include("${PROJECT_SOURCE_DIR}/cmake/platform_linux_desktop.cmake") else() message(FATAL_ERROR "Unable to determine a supported platform from ${CMAKE_SYSTEM_NAME}") -endif(MSVC) +endif() check_threading() setup_globals() @@ -333,60 +340,62 @@ install(FILES ${FLEECE_HEADERS} DESTINATION include/fleece) ### Support Libraries (Add functionality, but add nothing to official API) -add_subdirectory(REST EXCLUDE_FROM_ALL) - -set( - LC_WEBSOCKET_SRC - Networking/HTTP/HTTPTypes.cc - Networking/HTTP/HTTPLogic.cc - Networking/NetworkInterfaces.cc - Networking/Poller.cc - Networking/TCPSocket.cc - Networking/TLSContext.cc - Networking/WebSockets/BuiltInWebSocket.cc - vendor/sockpp/src/acceptor.cpp - vendor/sockpp/src/connector.cpp - vendor/sockpp/src/datagram_socket.cpp - vendor/sockpp/src/exception.cpp - vendor/sockpp/src/inet_address.cpp - vendor/sockpp/src/inet6_address.cpp - vendor/sockpp/src/mbedtls_context.cpp - vendor/sockpp/src/socket.cpp - vendor/sockpp/src/stream_socket.cpp -) +if (NOT EMSCRIPTEN) + add_subdirectory(REST EXCLUDE_FROM_ALL) -add_library(LiteCoreWebSocket STATIC EXCLUDE_FROM_ALL ${LC_WEBSOCKET_SRC}) -target_include_directories( - LiteCoreWebSocket PRIVATE - C - C/include - C/Cpp_include - Crypto - LiteCore/Support - Networking - Networking/BLIP/ - Networking/HTTP - Networking/WebSockets - Replicator - REST - vendor/fleece/Fleece/Support - vendor/fleece/API - vendor/sockpp/include - vendor/mbedtls/include - vendor/mbedtls/crypto/include -) + set( + LC_WEBSOCKET_SRC + Networking/HTTP/HTTPTypes.cc + Networking/HTTP/HTTPLogic.cc + Networking/NetworkInterfaces.cc + Networking/Poller.cc + Networking/TCPSocket.cc + Networking/TLSContext.cc + Networking/WebSockets/BuiltInWebSocket.cc + vendor/sockpp/src/acceptor.cpp + vendor/sockpp/src/connector.cpp + vendor/sockpp/src/datagram_socket.cpp + vendor/sockpp/src/exception.cpp + vendor/sockpp/src/inet_address.cpp + vendor/sockpp/src/inet6_address.cpp + vendor/sockpp/src/mbedtls_context.cpp + vendor/sockpp/src/socket.cpp + vendor/sockpp/src/stream_socket.cpp + ) -target_link_libraries( - LiteCoreWebSocket PUBLIC - LiteCoreObjects -) + add_library(LiteCoreWebSocket STATIC EXCLUDE_FROM_ALL ${LC_WEBSOCKET_SRC}) + target_include_directories( + LiteCoreWebSocket PRIVATE + C + C/include + C/Cpp_include + Crypto + LiteCore/Support + Networking + Networking/BLIP/ + Networking/HTTP + Networking/WebSockets + Replicator + REST + vendor/fleece/Fleece/Support + vendor/fleece/API + vendor/sockpp/include + vendor/mbedtls/include + vendor/mbedtls/crypto/include + ) -if(LITECORE_PERF_TESTING_MODE) - target_compile_definitions( + target_link_libraries( LiteCoreWebSocket PUBLIC - LITECORE_PERF_TESTING_MODE + LiteCoreObjects ) -endif() + + if(LITECORE_PERF_TESTING_MODE) + target_compile_definitions( + LiteCoreWebSocket PUBLIC + LITECORE_PERF_TESTING_MODE + ) + endif() +endif() # NOT EMSCRIPTEN ### TESTS: diff --git a/LiteCore/Storage/UnicodeCollator_JS.cc b/LiteCore/Storage/UnicodeCollator_JS.cc new file mode 100644 index 0000000000..87d817fc3c --- /dev/null +++ b/LiteCore/Storage/UnicodeCollator_JS.cc @@ -0,0 +1,98 @@ +// +// UnicodeCollator_JS.cc +// +// Copyright 2017-Present Couchbase, Inc. +// +// Use of this software is governed by the Business Source License included +// in the file licenses/BSL-Couchbase.txt. As of the Change Date specified +// in that file, in accordance with the Business Source License, use of this +// software will be governed by the Apache License, Version 2.0, included in +// the file licenses/APL2.txt. +// + +// This is an UnicodeCollaction implementation based on the JS Intl.Collator API. +// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Collator + +#include "UnicodeCollator.hh" +#include "Error.hh" +#include "emscripten/val.h" +#include "SQLiteCpp/Exception.h" +#include + +namespace litecore { + + using namespace std; + using namespace emscripten; + using namespace fleece; + + class JSCollationContext : public CollationContext { + public: + val collator = val::undefined(); + + JSCollationContext(const Collation& collation) : CollationContext(collation) { + auto locale = val::undefined(); + auto options = val::object(); + + if ( collation.localeName ) { locale = val(collation.localeName.asString()); } + + if ( collation.diacriticSensitive ) { + if ( collation.caseSensitive ) { + options.set("sensitivity", "variant"); + } else { + options.set("sensitivity", "accent"); + } + } else { + if ( collation.caseSensitive ) { + options.set("sensitivity", "case"); + } else { + options.set("sensitivity", "base"); + } + } + + collator = val::global("Intl")["Collator"].new_(locale, options); + } + }; + + unique_ptr CollationContext::create(const Collation& coll) { + return make_unique(coll); + } + + static inline int compareStringsUnicode(int len1, const void* chars1, int len2, const void* chars2, + const JSCollationContext& ctx) { + return ctx.collator.call("compare", string((const char*)chars1, len1), string((const char*)chars2, len2)); + } + + static int collateUnicodeCallback(void* context, int len1, const void* chars1, int len2, const void* chars2) { + auto& coll = *(JSCollationContext*)context; + if ( coll.canCompareASCII ) { + int result = CompareASCII(len1, (const uint8_t*)chars1, len2, (const uint8_t*)chars2, coll.caseSensitive); + if ( result != kCompareASCIIGaveUp ) return result; + } + return compareStringsUnicode(len1, chars1, len2, chars2, coll); + } + + int CompareUTF8(slice str1, slice str2, const Collation& coll) { + return CompareUTF8(str1, str2, JSCollationContext(coll)); + } + + int CompareUTF8(slice str1, slice str2, const CollationContext& ctx) { + return collateUnicodeCallback((void*)&ctx, (int)str1.size, str1.buf, (int)str2.size, str2.buf); + } + + int LikeUTF8(slice str1, slice str2, const Collation& coll) { + return LikeUTF8(str1, str2, JSCollationContext(coll)); + } + + bool ContainsUTF8(slice str, slice substr, const CollationContext& ctx) { + return ContainsUTF8_Slow(str, substr, ctx); + } + + unique_ptr RegisterSQLiteUnicodeCollation(sqlite3* dbHandle, const Collation& coll) { + unique_ptr context(new JSCollationContext(coll)); + int rc = sqlite3_create_collation(dbHandle, coll.sqliteName().c_str(), SQLITE_UTF8, (void*)context.get(), + collateUnicodeCallback); + if ( rc != SQLITE_OK ) throw SQLite::Exception(dbHandle, rc); + return context; + } + +} // namespace litecore diff --git a/LiteCore/Support/Error.cc b/LiteCore/Support/Error.cc index 071481398d..1254749590 100644 --- a/LiteCore/Support/Error.cc +++ b/LiteCore/Support/Error.cc @@ -37,8 +37,8 @@ # include #endif -#if defined(__clang__) && !defined(__ANDROID__) // For logBacktrace: -# include // Not available in Windows? +#if defined(__clang__) && !defined(__ANDROID__) && !defined(__EMSCRIPTEN__) // For logBacktrace: +# include // Not available in Windows? # include #endif diff --git a/LiteCore/Support/FilePath.cc b/LiteCore/Support/FilePath.cc index 8353dd357a..72d3cda3f5 100644 --- a/LiteCore/Support/FilePath.cc +++ b/LiteCore/Support/FilePath.cc @@ -50,7 +50,7 @@ using namespace std; using namespace fleece; using namespace litecore; -#ifdef __linux__ +#if defined(__linux__) || defined(__EMSCRIPTEN__) static int copyfile(const char* from, const char* to) { int read_fd, write_fd; off_t offset = 0; @@ -73,6 +73,30 @@ static int copyfile(const char* from, const char* to) { return write_fd; } +# ifdef __EMSCRIPTEN__ + static const size_t kBufSize = 1024; + uint8_t buf[kBufSize]; + while ( offset < stat_buf.st_size ) { + auto bytes_left_to_read = (size_t)(stat_buf.st_size - offset); + auto max_bytes_to_read = std::min(bytes_left_to_read, kBufSize); + ssize_t bytes_read; + if ( (bytes_read = read(read_fd, &buf, max_bytes_to_read)) < 0 ) { + int e = errno; + close(read_fd); + close(write_fd); + errno = e; + return -1; + } + offset = offset + bytes_read; + if ( write(write_fd, &buf, bytes_read) < 0 ) { + int e = errno; + close(read_fd); + close(write_fd); + errno = e; + return -1; + } + } +# else size_t expected = stat_buf.st_size; ssize_t bytes = 0; while ( bytes < expected ) { @@ -98,6 +122,7 @@ static int copyfile(const char* from, const char* to) { return -1; } } +# endif if ( close(read_fd) < 0 ) { int e = errno; diff --git a/LiteCore/Support/LogDecoder.cc b/LiteCore/Support/LogDecoder.cc index 9eac8f8c94..0c8f1daa44 100644 --- a/LiteCore/Support/LogDecoder.cc +++ b/LiteCore/Support/LogDecoder.cc @@ -46,7 +46,7 @@ namespace litecore { auto now = time_point_cast(system_clock::now()); auto count = now.time_since_epoch().count(); - time_t secs = (time_t)count / 1000000; + time_t secs = (time_t)(count / 1000000); unsigned microsecs = count % 1000000; return {secs, microsecs}; } diff --git a/LiteCore/Support/Logging.cc b/LiteCore/Support/Logging.cc index 38be15c6ee..8e246c9e16 100644 --- a/LiteCore/Support/Logging.cc +++ b/LiteCore/Support/Logging.cc @@ -29,7 +29,7 @@ # include #endif -#if ( defined(__linux__) || defined(__APPLE__) ) && !defined(__ANDROID__) +#if ( defined(__linux__) || defined(__APPLE__) || defined(__EMSCRIPTEN__) ) && !defined(__ANDROID__) # include #endif @@ -625,7 +625,7 @@ namespace litecore { static std::string classNameOf(const Logging* obj) { const char* name = typeid(*obj).name(); -#if ( defined(__linux__) || defined(__APPLE__) ) && !defined(__ANDROID__) +#if ( defined(__linux__) || defined(__APPLE__) || defined(__EMSCRIPTEN__) ) && !defined(__ANDROID__) // Get the name of my class, unmangle it, and remove namespaces: size_t unmangledLen; int status; diff --git a/LiteCore/Support/MultiLogDecoder.hh b/LiteCore/Support/MultiLogDecoder.hh index eb2365a343..db45cec437 100644 --- a/LiteCore/Support/MultiLogDecoder.hh +++ b/LiteCore/Support/MultiLogDecoder.hh @@ -14,6 +14,7 @@ #include "LogDecoder.hh" #include #include +#include #include #include #include @@ -27,8 +28,9 @@ namespace litecore { class MultiLogDecoder : public LogIterator { public: MultiLogDecoder() { - _startTime = {UINT_MAX, 0}; - for ( unsigned i = 0; i <= kMaxLevel; i++ ) _startTimeByLevel[i] = {UINT_MAX, 0}; + auto max_time_t = std::numeric_limits::max(); + _startTime = {max_time_t, 0}; + for ( unsigned i = 0; i <= kMaxLevel; i++ ) _startTimeByLevel[i] = {max_time_t, 0}; } /// Adds a log iterator. Must be called before calling \ref next(). diff --git a/LiteCore/Support/StringUtil.cc b/LiteCore/Support/StringUtil.cc index 93358cfdae..f06303a48f 100644 --- a/LiteCore/Support/StringUtil.cc +++ b/LiteCore/Support/StringUtil.cc @@ -36,7 +36,7 @@ namespace litecore { using namespace fleece; -#if defined(__ANDROID__) || defined(__GLIBC__) || defined(_MSC_VER) +#if defined(__ANDROID__) || defined(__GLIBC__) || defined(_MSC_VER) || defined(__EMSCRIPTEN__) // digittoint is a BSD function, not available on Android, Linux, etc. int digittoint(char ch) { int d = ch - '0'; @@ -264,7 +264,7 @@ namespace litecore { } -#if !__APPLE__ && !defined(_MSC_VER) && !LITECORE_USES_ICU +#if !__APPLE__ && !defined(_MSC_VER) && !LITECORE_USES_ICU && !defined(__EMSCRIPTEN__) // Stub implementation for when case conversion is unavailable alloc_slice UTF8ChangeCase(slice str, bool toUppercase) { diff --git a/LiteCore/Support/StringUtil.hh b/LiteCore/Support/StringUtil.hh index 3b9dc2351f..ccf17b1e2a 100644 --- a/LiteCore/Support/StringUtil.hh +++ b/LiteCore/Support/StringUtil.hh @@ -27,7 +27,7 @@ namespace litecore { using namespace fleece; -#if defined(__ANDROID__) || defined(__GLIBC__) || defined(_MSC_VER) +#if defined(__ANDROID__) || defined(__GLIBC__) || defined(_MSC_VER) || defined(__EMSCRIPTEN__) // Converts a decimal or hex digit to its integer equivalent (0..15), or 0 if not a digit. // (This function is part of in BSD and Apple OSs.) int digittoint(char ch); diff --git a/LiteCore/Support/StringUtil_JS.cc b/LiteCore/Support/StringUtil_JS.cc new file mode 100644 index 0000000000..710082cde4 --- /dev/null +++ b/LiteCore/Support/StringUtil_JS.cc @@ -0,0 +1,29 @@ +// +// StringUtil_JS.cc +// +// Copyright 2017-Present Couchbase, Inc. +// +// Use of this software is governed by the Business Source License included +// in the file licenses/BSL-Couchbase.txt. As of the Change Date specified +// in that file, in accordance with the Business Source License, use of this +// software will be governed by the Apache License, Version 2.0, included in +// the file licenses/APL2.txt. +// + +#include "StringUtil.hh" +#include "emscripten/val.h" + +namespace litecore { + using namespace fleece; + + alloc_slice UTF8ChangeCase(slice str, bool toUppercase) { + auto strVal = emscripten::val(str.asString()); + std::string resultStr; + if ( toUppercase ) { + resultStr = strVal.call("toUpperCase"); + } else { + resultStr = strVal.call("toLowerCase"); + } + return alloc_slice(resultStr); + } +} // namespace litecore \ No newline at end of file diff --git a/LiteCore/Support/ThreadUtil.cc b/LiteCore/Support/ThreadUtil.cc index ff87d02205..4809aabd7a 100644 --- a/LiteCore/Support/ThreadUtil.cc +++ b/LiteCore/Support/ThreadUtil.cc @@ -33,7 +33,9 @@ # ifndef HAVE_PTHREAD_THREADID_NP # include # endif -# ifndef HAVE_PTHREAD_GETNAME_NP +# ifdef __EMSCRIPTEN__ +# include +# elif !defined(HAVE_PTHREAD_GETNAME_NP) # include # endif @@ -42,6 +44,8 @@ namespace litecore { void SetThreadName(const char* name) { # ifdef __APPLE__ pthread_setname_np(name); +# elif defined(__EMSCRIPTEN__) + emscripten_set_thread_name(pthread_self(), name); # else pthread_setname_np(pthread_self(), name); # endif @@ -50,10 +54,11 @@ namespace litecore { std::string GetThreadName() { std::string retVal; std::stringstream s; - char name[256]; # if defined(HAVE_PTHREAD_GETNAME_NP) + char name[256]; if ( pthread_getname_np(pthread_self(), name, 255) == 0 && name[0] != 0 ) { s << name << " "; } # elif defined(HAVE_PRCTL) + char name[256]; if ( prctl(PR_GET_NAME, name, 0, 0, 0) == 0 ) { s << name << " "; } # else s << " "; @@ -70,6 +75,8 @@ namespace litecore { tid = syscall(SYS_gettid); # elif defined(HAVE_NR_GETTID) tid = syscall(__NR_gettid); +# elif defined(__EMSCRIPTEN__) + tid = gettid(); # endif s << "(" << tid << ")"; diff --git a/LiteCore/tests/CMakeLists.txt b/LiteCore/tests/CMakeLists.txt index f778594233..4ff8773da7 100644 --- a/LiteCore/tests/CMakeLists.txt +++ b/LiteCore/tests/CMakeLists.txt @@ -38,6 +38,8 @@ elseif(APPLE) include("${CMAKE_CURRENT_LIST_DIR}/cmake/platform_apple.cmake") elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") include("${CMAKE_CURRENT_LIST_DIR}/cmake/platform_linux.cmake") +elseif(EMSCRIPTEN) + include("${CMAKE_CURRENT_LIST_DIR}/cmake/platform_emscripten.cmake") else() message(FATAL_ERROR "Unsupported platform ${CMAKE_SYSTEM_NAME}") endif() @@ -69,9 +71,6 @@ add_executable( UpgraderTest.cc VectorQueryTest.cc VersionVectorTest.cc - ${TOP}REST/tests/RESTListenerTest.cc - ${TOP}REST/tests/RESTClientTest.cc - ${TOP}REST/tests/SyncListenerTest.cc ${TOP}vendor/fleece/Tests/API_ValueTests.cc ${TOP}vendor/fleece/Tests/DeltaTests.cc ${TOP}vendor/fleece/Tests/EncoderTests.cc @@ -87,13 +86,8 @@ add_executable( ${TOP}Replicator/tests/DBAccessTestWrapper.cc ${TOP}Replicator/tests/PropertyEncryptionTests.cc ${TOP}Replicator/tests/ReplicatorLoopbackTest.cc - ${TOP}Replicator/tests/ReplicatorAPITest.cc - ${TOP}Replicator/tests/ReplicatorSGTest.cc ${TOP}Replicator/tests/ReplicatorCollectionTest.cc - ${TOP}Replicator/tests/ReplicatorCollectionSGTest.cc - ${TOP}Replicator/tests/ReplicatorSG30Test.cc ${TOP}Replicator/tests/ReplicatorVVUpgradeTest.cc - ${TOP}Replicator/tests/SG.cc ${TOP}Replicator/tests/SGTestUser.cc ${TOP}Replicator/tests/ReplParams.cc ${TOP}C/tests/c4Test.cc @@ -103,6 +97,20 @@ add_executable( main.cpp ) +if (NOT EMSCRIPTEN) + target_sources( + CppTests PRIVATE + ${TOP}REST/tests/RESTListenerTest.cc + ${TOP}REST/tests/RESTClientTest.cc + ${TOP}REST/tests/SyncListenerTest.cc + ${TOP}Replicator/tests/ReplicatorAPITest.cc + ${TOP}Replicator/tests/ReplicatorSGTest.cc + ${TOP}Replicator/tests/ReplicatorCollectionSGTest.cc + ${TOP}Replicator/tests/ReplicatorSG30Test.cc + ${TOP}Replicator/tests/SG.cc + ) +endif() + get_directory_property(this_targets DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} BUILDSYSTEM_TARGETS) set(LITECORE_TARGETS ${LITECORE_TARGETS} ${this_targets} PARENT_SCOPE) setup_build() @@ -157,6 +165,12 @@ target_link_libraries( LiteCoreUnitTesting FleeceObjects BLIPObjects - LiteCoreREST_Static - LiteCoreWebSocket ) + +if (NOT EMSCRIPTEN) + target_link_libraries( + CppTests PRIVATE + LiteCoreREST_Static + LiteCoreWebSocket + ) +endif() diff --git a/LiteCore/tests/DataFileTest.cc b/LiteCore/tests/DataFileTest.cc index b81cad8fcb..9c23961afe 100644 --- a/LiteCore/tests/DataFileTest.cc +++ b/LiteCore/tests/DataFileTest.cc @@ -20,6 +20,9 @@ #ifndef _MSC_VER # include #endif +#ifdef __EMSCRIPTEN__ +# include +#endif #include "LiteCoreTest.hh" #include @@ -645,18 +648,31 @@ N_WAY_TEST_CASE_METHOD(DataFileTestFixture, "DataFile Compact", "[DataFile]") { } TEST_CASE("CanonicalPath") { +#ifdef __EMSCRIPTEN__ + // clang-format off + auto isMacOs = (bool)EM_ASM_INT({ + if ( typeof require === 'undefined' ) return false; + const process = require('process'); + return process.platform === 'darwin'; + }); + // clang-format on +#endif + #ifdef _MSC_VER const char* startPath = "C:\\folder\\..\\subfolder\\"; string endPath = "C:\\subfolder\\"; #else auto tmpPath = TestFixture::sTempDir.path(); auto startPath = tmpPath + "folder/"; - ::mkdir(startPath.c_str(), 777); + ::mkdir(startPath.c_str(), 0777); startPath += "../subfolder/"; auto endPath = tmpPath + "subfolder"; - ::mkdir(endPath.c_str(), 777); -# if __APPLE__ && !TARGET_OS_IPHONE - endPath = "/private" + endPath; + ::mkdir(endPath.c_str(), 0777); +# if __APPLE__ && !TARGET_OS_IPHONE || defined(__EMSCRIPTEN__) +# ifdef __EMSCRIPTEN__ + if ( isMacOs ) +# endif + endPath = "/private" + endPath; # endif #endif @@ -668,10 +684,13 @@ TEST_CASE("CanonicalPath") { endPath = startPath; #else startPath = tmpPath + u8"日本語"; - ::mkdir(startPath.c_str(), 777); + ::mkdir(startPath.c_str(), 0777); endPath = startPath; -# if __APPLE__ && !TARGET_OS_IPHONE - endPath = "/private" + endPath; +# if __APPLE__ && !TARGET_OS_IPHONE || defined(__EMSCRIPTEN__) +# ifdef __EMSCRIPTEN__ + if ( isMacOs ) +# endif + endPath = "/private" + endPath; # endif #endif diff --git a/LiteCore/tests/LiteCoreTest.hh b/LiteCore/tests/LiteCoreTest.hh index 3df7faff87..7525c87cad 100644 --- a/LiteCore/tests/LiteCoreTest.hh +++ b/LiteCore/tests/LiteCoreTest.hh @@ -51,7 +51,7 @@ std::string stringWithFormat(const char* format, ...) __printflike(1, 2); */ template static std::string randomDigitString() { - static_assert(1 < numDigits <= 64); + static_assert(1 < numDigits && numDigits <= 64); static_assert(numDigits % 2 == 0); auto appendEightDigits = [](std::stringstream& sstr) { auto now = std::chrono::high_resolution_clock::now(); diff --git a/LiteCore/tests/SQLiteFunctionsTest.cc b/LiteCore/tests/SQLiteFunctionsTest.cc index b869d04e84..77f0aff568 100644 --- a/LiteCore/tests/SQLiteFunctionsTest.cc +++ b/LiteCore/tests/SQLiteFunctionsTest.cc @@ -328,7 +328,7 @@ TEST_CASE("Unicode string functions", "[Query]") { CHECK(UTF8ChangeCase("E"_sl, true) == "E"_sl); CHECK(UTF8ChangeCase("-"_sl, true) == "-"_sl); CHECK(UTF8ChangeCase("Z•rGMai2"_sl, true) == "Z•RGMAI2"_sl); -#if __APPLE__ || defined(_MSC_VER) || LITECORE_USES_ICU +#if __APPLE__ || defined(_MSC_VER) || LITECORE_USES_ICU || defined(__EMSCRIPTEN__) CHECK(UTF8ChangeCase("Zérgmåī2"_sl, true) == "ZÉRGMÅĪ2"_sl); #endif CHECK(UTF8ChangeCase("😀"_sl, true) == "😀"_sl); @@ -338,7 +338,7 @@ TEST_CASE("Unicode string functions", "[Query]") { CHECK(UTF8ChangeCase("e"_sl, false) == "e"_sl); CHECK(UTF8ChangeCase("-"_sl, false) == "-"_sl); CHECK(UTF8ChangeCase("Z•rGMai2"_sl, false) == "z•rgmai2"_sl); -#if __APPLE__ || defined(_MSC_VER) || LITECORE_USES_ICU +#if __APPLE__ || defined(_MSC_VER) || LITECORE_USES_ICU || defined(__EMSCRIPTEN__) CHECK(UTF8ChangeCase("zÉRGMÅĪ2"_sl, false) == "zérgmåī2"_sl); #endif CHECK(UTF8ChangeCase("😀"_sl, false) == "😀"_sl); @@ -362,7 +362,7 @@ N_WAY_TEST_CASE_METHOD(SQLiteFunctionsTest, "N1QL string functions", "[Query]") CHECK(query("SELECT N1QL_lower('cAFES17•')") == (vector{"cafes17•"})); CHECK(query("SELECT N1QL_upper('cafes17')") == (vector{"CAFES17"})); -#if __APPLE__ || defined(_MSC_VER) || LITECORE_USES_ICU +#if __APPLE__ || defined(_MSC_VER) || LITECORE_USES_ICU || defined(__EMSCRIPTEN__) CHECK(query("SELECT N1QL_lower('cAFÉS17•')") == (vector{"cafés17•"})); CHECK(query("SELECT N1QL_upper('cafés17')") == (vector{"CAFÉS17"})); #endif @@ -385,7 +385,7 @@ N_WAY_TEST_CASE_METHOD(SQLiteFunctionsTest, "SQLite fl_blob", "[Query]") { #pragma mark - COLLATION: -#if __APPLE__ || defined(_MSC_VER) || LITECORE_USES_ICU +#if __APPLE__ || defined(_MSC_VER) || LITECORE_USES_ICU || defined(__EMSCRIPTEN__) TEST_CASE("Unicode collation", "[Query][Collation]") { struct { slice a; diff --git a/LiteCore/tests/cmake/platform_emscripten.cmake b/LiteCore/tests/cmake/platform_emscripten.cmake new file mode 100644 index 0000000000..d7825dd918 --- /dev/null +++ b/LiteCore/tests/cmake/platform_emscripten.cmake @@ -0,0 +1,23 @@ +function(setup_build) + target_include_directories( + CppTests PRIVATE + ${TOP}vendor/mbedtls/include + ${TOP}LiteCore/Unix + ) + + target_compile_options( + CppTests PRIVATE + ${EMSCRIPTEN_COMPILE_FLAGS} + ) + + target_link_options( + CppTests PRIVATE + ${EMSCRIPTEN_LINK_FLAGS} + "SHELL:-s EXIT_RUNTIME=1" + "SHELL:-s PTHREAD_POOL_SIZE=24" + "SHELL:-s ALLOW_MEMORY_GROWTH=1" + "SHELL:-s WASM_BIGINT=1" + "-lnodefs.js" + "-lnoderawfs.js" + ) +endfunction() diff --git a/Networking/BLIP/CMakeLists.txt b/Networking/BLIP/CMakeLists.txt index ad75ed6131..6ff1ab1deb 100644 --- a/Networking/BLIP/CMakeLists.txt +++ b/Networking/BLIP/CMakeLists.txt @@ -42,6 +42,8 @@ elseif(ANDROID) include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/platform_android.cmake") elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/platform_linux.cmake") +elseif(EMSCRIPTEN) + include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/platform_emscripten.cmake") else() message(FATAL_ERROR "Unsupported platform ${CMAKE_SYSTEM_NAME}!") endif() diff --git a/Networking/BLIP/cmake/platform_emscripten.cmake b/Networking/BLIP/cmake/platform_emscripten.cmake new file mode 100644 index 0000000000..0e17ba4641 --- /dev/null +++ b/Networking/BLIP/cmake/platform_emscripten.cmake @@ -0,0 +1,32 @@ +include("${CMAKE_CURRENT_LIST_DIR}/platform_base.cmake") + +function(set_source_files) + set(oneValueArgs RESULT) + cmake_parse_arguments(EMSCRIPTEN_SSS "" "${oneValueArgs}" "" ${ARGN}) + if(NOT DEFINED EMSCRIPTEN_SSS_RESULT) + message(FATAL_ERROR "set_source_files needs to be called with RESULT") + endif() + + set_source_files_base(RESULT BASE_SRC_FILES) + set( + ${EMSCRIPTEN_SSS_RESULT} + ${BASE_SRC_FILES} + ${SUPPORT_LOCATION}/ThreadedMailbox.cc + PARENT_SCOPE + ) +endfunction() + +function(setup_build) + add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/../../vendor/zlib" "vendor/zlib") + target_include_directories( + BLIPObjects PRIVATE + "${CMAKE_CURRENT_LIST_DIR}/../../vendor/zlib" + "${CMAKE_CURRENT_BINARY_DIR}/vendor/zlib" + ${LITECORE_LOCATION}/LiteCore/Unix + ) + + target_link_libraries( + BLIPObjects INTERFACE + zlibstatic + ) +endfunction() diff --git a/Networking/NetworkInterfaces.cc b/Networking/NetworkInterfaces.cc index de0b94ae97..fad2766421 100644 --- a/Networking/NetworkInterfaces.cc +++ b/Networking/NetworkInterfaces.cc @@ -100,6 +100,8 @@ namespace litecore::net { const auto firstBytePair = addr6().__u6_addr.__u6_addr16[0]; #elif defined(__ANDROID__) const auto firstBytePair = addr6().in6_u.u6_addr16[0]; +#elif defined(__EMSCRIPTEN__) + const auto firstBytePair = addr6().__in6_union.__s6_addr16[0]; #else const auto firstBytePair = addr6().__in6_u.__u6_addr16[0]; #endif diff --git a/REST/tests/RESTListenerTest.cc b/REST/tests/RESTListenerTest.cc index 47075aa4a2..dab67827ed 100644 --- a/REST/tests/RESTListenerTest.cc +++ b/REST/tests/RESTListenerTest.cc @@ -30,11 +30,14 @@ using namespace litecore::REST; using namespace std; -#if !defined(_WIN32) || defined(_WIN64) +#if ( !defined(_WIN32) || defined(_WIN64) ) && !defined(__EMSCRIPTEN__) // These tests often hang in 32-bit Windows but let's cheekily ignore it since // this not part of Couchbase Lite directly anyway, but used in the cblite CLI // which is 64-bit only on Windows. +// TODO: Port RESTListener to emscripten + + static string to_str(FLSlice s) { return {(char*)s.buf, s.size}; } static string to_str(Value v) { return to_str(v.asString()); } @@ -864,4 +867,4 @@ TEST_CASE_METHOD(C4RESTTest, "REST HTTP Replicate Oneshot, Auth", "[REST][Listen # endif // COUCHBASE_ENTERPRISE -#endif +#endif // _WIN32, _Win64, __EMSCRIPTEN__ diff --git a/Replicator/tests/CookieStoreTest.cc b/Replicator/tests/CookieStoreTest.cc index f5c06042c2..b2739cba61 100644 --- a/Replicator/tests/CookieStoreTest.cc +++ b/Replicator/tests/CookieStoreTest.cc @@ -184,6 +184,7 @@ TEST_CASE("Cookie Parser Failure", "[cookies]") { "name=value; Max-Age=", }; for ( const auto& badCookie : badCookies ) { + ExpectingExceptions x; INFO("Checking " << badCookie); Cookie c(badCookie, "example.com", "/"); CHECK(!c); diff --git a/Xcode/LiteCore.xcodeproj/project.pbxproj b/Xcode/LiteCore.xcodeproj/project.pbxproj index 8ae6aaf999..0fda1f47b4 100644 --- a/Xcode/LiteCore.xcodeproj/project.pbxproj +++ b/Xcode/LiteCore.xcodeproj/project.pbxproj @@ -1179,6 +1179,10 @@ 275FF6661E42A90C005F90DD /* QueryParserTables.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = QueryParserTables.hh; sourceTree = ""; }; 275FF6D11E4947E1005F90DD /* c4BaseTest.cc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = c4BaseTest.cc; sourceTree = ""; }; 2760FBC826210AA0000F34C5 /* CollectionImpl.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CollectionImpl.hh; sourceTree = ""; }; + 27614AA12C1CDCCB0065FBB5 /* cmake */ = {isa = PBXFileReference; lastKnownFileType = folder; name = cmake; path = ../cmake; sourceTree = ""; }; + 27614AA52C1CDCEA0065FBB5 /* cmake */ = {isa = PBXFileReference; lastKnownFileType = folder; path = cmake; sourceTree = ""; }; + 27614AA62C1CDD110065FBB5 /* cmake */ = {isa = PBXFileReference; lastKnownFileType = folder; path = cmake; sourceTree = ""; }; + 27614AA72C1CDDB00065FBB5 /* cmake */ = {isa = PBXFileReference; lastKnownFileType = folder; path = cmake; sourceTree = ""; }; 2761F3EE1EE9CC58006D4BB8 /* CookieStore.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CookieStore.cc; sourceTree = ""; }; 2761F3EF1EE9CC58006D4BB8 /* CookieStore.hh */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = CookieStore.hh; sourceTree = ""; }; 2761F3F61EEA00C3006D4BB8 /* CookieStoreTest.cc */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = CookieStoreTest.cc; sourceTree = ""; }; @@ -1915,6 +1919,7 @@ 2708FE591CF4D0450022F721 /* LiteCoreTest.hh */, 274D040A1BA75E1C00FF7C35 /* main.cpp */, 2750735A1F4B5ECC003D2CCE /* CMakeLists.txt */, + 27614AA72C1CDDB00065FBB5 /* cmake */, 2719253B23970F4E0053DDA6 /* replacedb */, ); name = "C++ Tests"; @@ -2101,6 +2106,7 @@ children = ( 2744B329241854F2005A194D /* README.md */, 2744B30C241854F2005A194D /* CMakeLists.txt */, + 27614AA62C1CDD110065FBB5 /* cmake */, 2744B317241854F2005A194D /* BLIPConnection.hh */, 2744B319241854F2005A194D /* BLIP.hh */, 2744B31C241854F2005A194D /* MessageBuilder.hh */, @@ -2234,6 +2240,7 @@ 2783DF981D27436700F84E6E /* c4ThreadingTest.cc */, 2700BB612170117F00797537 /* EE */, 275073591F4B5E66003D2CCE /* CMakeLists.txt */, + 27614AA52C1CDCEA0065FBB5 /* cmake */, 2719253323970C7F0053DDA6 /* data */, ); path = tests; @@ -2260,6 +2267,7 @@ children = ( 273AD3CF18F4B23A007D8C23 /* README.md */, 275073581F4B5E13003D2CCE /* CMakeLists.txt */, + 27614AA12C1CDCCB0065FBB5 /* cmake */, 2757DE551B9FC34E002EE261 /* API */, 2745B7CA26825F970012A17A /* docs */, 27E4871019217187007D8940 /* LiteCore */, diff --git a/build_cmake/scripts/build_tests_emscripten.sh b/build_cmake/scripts/build_tests_emscripten.sh new file mode 100755 index 0000000000..8784e68e4a --- /dev/null +++ b/build_cmake/scripts/build_tests_emscripten.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e + +rm -rf build +mkdir -p build +cd build + +emcmake cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo .. + +core_count=`getconf _NPROCESSORS_ONLN` +emmake make C4Tests -j `expr $core_count + 1` +emmake make CppTests -j `expr $core_count + 1` diff --git a/build_cmake/scripts/build_wasm_EE.sh b/build_cmake/scripts/build_wasm_EE.sh new file mode 100755 index 0000000000..8eadeeb9b3 --- /dev/null +++ b/build_cmake/scripts/build_wasm_EE.sh @@ -0,0 +1,19 @@ +#!/bin/bash + +# Copyright 2020-Present Couchbase, Inc. +# +# Use of this software is governed by the Business Source License included in +# the file licenses/BSL-Couchbase.txt. As of the Change Date specified in that +# file, in accordance with the Business Source License, use of this software +# will be governed by the Apache License, Version 2.0, included in the file +# licenses/APL2.txt. + +set -e + +SCRIPT_DIR=`dirname $0` +cd $SCRIPT_DIR/.. +mkdir -p wasm_EE +cd wasm_EE + +emcmake cmake -DBUILD_ENTERPRISE=ON -DCMAKE_BUILD_TYPE=Release ../.. +emmake make -j diff --git a/cmake/platform_emscripten.cmake b/cmake/platform_emscripten.cmake new file mode 100644 index 0000000000..fe7a61eae5 --- /dev/null +++ b/cmake/platform_emscripten.cmake @@ -0,0 +1,60 @@ +include("${CMAKE_CURRENT_LIST_DIR}/platform_unix.cmake") + +macro(check_threading) + message(STATUS "No threading checks needed for Emscripten") +endmacro() + +set(EMSCRIPTEN_COMPILE_FLAGS "-pthread" "-fwasm-exceptions" "-Os") +set(EMSCRIPTEN_LINK_FLAGS "-pthread" "-fwasm-exceptions" "-lembind") + +function(setup_globals) + setup_globals_unix() + + #if(NOT DISABLE_LTO_BUILD) + set(EMSCRIPTEN_COMPILE_FLAGS ${EMSCRIPTEN_COMPILE_FLAGS} "-flto" PARENT_SCOPE) + set(EMSCRIPTEN_LINK_FLAGS ${EMSCRIPTEN_LINK_FLAGS} "-flto" PARENT_SCOPE) + #endif() +endfunction() + +function(set_litecore_source) + set(oneValueArgs RESULT) + cmake_parse_arguments(EMSCRIPTEN_SSS "" ${oneValueArgs} "" ${ARGN}) + if(NOT DEFINED EMSCRIPTEN_SSS_RESULT) + message(FATAL_ERROR set_source_files_base needs to be called with RESULT) + endif() + + set_litecore_source_base(RESULT BASE_LITECORE_FILES) + set( + ${EMSCRIPTEN_SSS_RESULT} + ${BASE_LITECORE_FILES} + LiteCore/Storage/UnicodeCollator_JS.cc + LiteCore/Support/StringUtil_JS.cc + PARENT_SCOPE + ) +endfunction() + +function(setup_litecore_build) + setup_litecore_build_unix() + + target_include_directories( + LiteCoreStatic PRIVATE + LiteCore/Unix + ) + + message(STATUS "Emscripten flags are ${EMSCRIPTEN_COMPILE_FLAGS}") + foreach(platform LiteCoreObjects LiteCoreUnitTesting BLIPObjects CouchbaseSqlite3 SQLite3_UnicodeSN) + target_compile_options( + ${platform} PRIVATE + ${EMSCRIPTEN_COMPILE_FLAGS} + ) + endforeach() + + target_link_libraries( + LiteCoreStatic INTERFACE + zlibstatic + ) +endfunction() + +function(setup_rest_build) + setup_rest_build_unix() +endfunction() diff --git a/cmake/platform_unix.cmake b/cmake/platform_unix.cmake index 6bcfc0e6a0..9df1ac7324 100644 --- a/cmake/platform_unix.cmake +++ b/cmake/platform_unix.cmake @@ -79,24 +79,34 @@ function(setup_litecore_build_unix) target_compile_options(${liteCoreVariant} PRIVATE ${LITECORE_WARNINGS} -Wformat=2 - -fstack-protector -D_FORTIFY_SOURCE=2 $<$:-Wno-psabi;-Wno-odr> $<$:${LITECORE_CXX_WARNINGS}> $<$:${LITECORE_C_WARNINGS}> ) + if (NOT EMSCRIPTEN) + target_compile_options(${liteCoreVariant} PRIVATE + -fstack-protector + ) + endif() endforeach() target_compile_options(BLIPObjects PRIVATE ${LITECORE_WARNINGS} -Wformat=2 - -fstack-protector -D_FORTIFY_SOURCE=2 $<$:-Wno-psabi;-Wno-odr> $<$:${LITECORE_CXX_WARNINGS}> $<$:${LITECORE_C_WARNINGS}> ) + if (NOT EMSCRIPTEN) + target_compile_options(BLIPObjects PRIVATE + -fstack-protector + ) + endif() + + set(CMAKE_EXTRA_INCLUDE_FILES "sys/socket.h") check_type_size(socklen_t SOCKLEN_T) if(${HAVE_SOCKLEN_T}) diff --git a/scripts/run_tests_with_nodejs.sh b/scripts/run_tests_with_nodejs.sh new file mode 100755 index 0000000000..c71a885cad --- /dev/null +++ b/scripts/run_tests_with_nodejs.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +set -e + +node --experimental-wasm-eh build/C/tests/C4Tests.js -r list +node --experimental-wasm-eh build/LiteCore/tests/CppTests.js -r list diff --git a/vendor/fleece b/vendor/fleece index 0610a53af0..de2fdb29e4 160000 --- a/vendor/fleece +++ b/vendor/fleece @@ -1 +1 @@ -Subproject commit 0610a53af09b05ae9841cdc01cc339b157adbce0 +Subproject commit de2fdb29e419458367ee821ebc8096e0d6c74c7f